mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-3800: Get TerminalService working on Windows, too.
This commit is contained in:
parent
5bed356fd2
commit
a548e54075
37 changed files with 834 additions and 217 deletions
|
@ -79,6 +79,11 @@ public class GdbManagerImpl implements GdbManager {
|
||||||
private static final String GDB_IS_TERMINATING = "GDB is terminating";
|
private static final String GDB_IS_TERMINATING = "GDB is terminating";
|
||||||
public static final int MAX_CMD_LEN = 4094; // Account for longest possible line end
|
public static final int MAX_CMD_LEN = 4094; // Account for longest possible line end
|
||||||
|
|
||||||
|
private static final boolean IS_WINDOWS =
|
||||||
|
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS;
|
||||||
|
private static final short PTY_COLS = IS_WINDOWS ? Short.MAX_VALUE : 0;
|
||||||
|
private static final short PTY_ROWS = IS_WINDOWS ? (short) 1 : 0;
|
||||||
|
|
||||||
private String maintInfoSectionsCmd = GdbModuleImpl.MAINT_INFO_SECTIONS_CMD_V11;
|
private String maintInfoSectionsCmd = GdbModuleImpl.MAINT_INFO_SECTIONS_CMD_V11;
|
||||||
private Pattern fileLinePattern = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN_V11;
|
private Pattern fileLinePattern = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN_V11;
|
||||||
private Pattern sectionLinePattern = GdbModuleImpl.OBJECT_SECTION_LINE_PATTERN_V10;
|
private Pattern sectionLinePattern = GdbModuleImpl.OBJECT_SECTION_LINE_PATTERN_V10;
|
||||||
|
@ -119,7 +124,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||||
InputStream inputStream = pty.getParent().getInputStream();
|
InputStream inputStream = pty.getParent().getInputStream();
|
||||||
// TODO: This should really only be applied to the MI2 console
|
// TODO: This should really only be applied to the MI2 console
|
||||||
// But, we don't know what we have until we read it....
|
// But, we don't know what we have until we read it....
|
||||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) {
|
if (IS_WINDOWS) {
|
||||||
inputStream = new AnsiBufferedInputStream(inputStream);
|
inputStream = new AnsiBufferedInputStream(inputStream);
|
||||||
}
|
}
|
||||||
this.reader = new BufferedReader(new InputStreamReader(inputStream));
|
this.reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
|
@ -652,7 +657,8 @@ public class GdbManagerImpl implements GdbManager {
|
||||||
executor = Executors.newSingleThreadExecutor();
|
executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
if (gdbCmd != null) {
|
if (gdbCmd != null) {
|
||||||
iniThread = new PtyThread(ptyFactory.openpty(), Channel.STDOUT, null);
|
iniThread =
|
||||||
|
new PtyThread(ptyFactory.openpty(PTY_COLS, PTY_ROWS), Channel.STDOUT, null);
|
||||||
|
|
||||||
Msg.info(this, "Starting gdb with: " + fullargs);
|
Msg.info(this, "Starting gdb with: " + fullargs);
|
||||||
gdb =
|
gdb =
|
||||||
|
@ -708,7 +714,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Pty mi2Pty = ptyFactory.openpty();
|
Pty mi2Pty = ptyFactory.openpty(Short.MAX_VALUE, (short) 1);
|
||||||
String mi2PtyName = mi2Pty.getChild().nullSession(Echo.OFF);
|
String mi2PtyName = mi2Pty.getChild().nullSession(Echo.OFF);
|
||||||
Msg.info(this, "Agent is waiting for GDB/MI v2 interpreter at " + mi2PtyName);
|
Msg.info(this, "Agent is waiting for GDB/MI v2 interpreter at " + mi2PtyName);
|
||||||
mi2Thread = new PtyThread(mi2Pty, Channel.STDOUT, Interpreter.MI2);
|
mi2Thread = new PtyThread(mi2Pty, Channel.STDOUT, Interpreter.MI2);
|
||||||
|
|
|
@ -40,7 +40,6 @@ import generic.ULongSpan.ULongSpanSet;
|
||||||
import ghidra.async.AsyncReference;
|
import ghidra.async.AsyncReference;
|
||||||
import ghidra.dbg.testutil.DummyProc;
|
import ghidra.dbg.testutil.DummyProc;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
import ghidra.pty.linux.LinuxPtyFactory;
|
|
||||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
|
@ -62,8 +61,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PtyFactory getPtyFactory() {
|
protected PtyFactory getPtyFactory() {
|
||||||
// TODO: Choose by host OS
|
return PtyFactory.local();
|
||||||
return new LinuxPtyFactory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract CompletableFuture<Void> startManager(GdbManager manager);
|
protected abstract CompletableFuture<Void> startManager(GdbManager manager);
|
||||||
|
|
|
@ -21,8 +21,6 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
||||||
import agent.gdb.manager.GdbManager;
|
import agent.gdb.manager.GdbManager;
|
||||||
import ghidra.pty.PtyFactory;
|
|
||||||
import ghidra.pty.windows.ConPtyFactory;
|
|
||||||
|
|
||||||
@Ignore("Need compatible version on CI")
|
@Ignore("Need compatible version on CI")
|
||||||
public class SpawnedWindowsMi2GdbManagerTest extends AbstractGdbManagerTest {
|
public class SpawnedWindowsMi2GdbManagerTest extends AbstractGdbManagerTest {
|
||||||
|
@ -36,10 +34,4 @@ public class SpawnedWindowsMi2GdbManagerTest extends AbstractGdbManagerTest {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PtyFactory getPtyFactory() {
|
|
||||||
// TODO: Choose by host OS
|
|
||||||
return new ConPtyFactory();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,11 +39,11 @@ public class TerminalGhidraScript extends GhidraScript {
|
||||||
|
|
||||||
protected void displayInTerminal(PtyParent parent, Runnable waiter) throws PluginException {
|
protected void displayInTerminal(PtyParent parent, Runnable waiter) throws PluginException {
|
||||||
TerminalService terminalService = ensureTerminalService();
|
TerminalService terminalService = ensureTerminalService();
|
||||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("US-ASCII"),
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream())) {
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
term.addTerminalListener(new TerminalListener() {
|
term.addTerminalListener(new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(int cols, int rows) {
|
public void resized(short cols, short rows) {
|
||||||
parent.setWindowSize(cols, rows);
|
parent.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,12 +56,62 @@ public class DefaultTerminal implements Terminal {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFixedSize(int rows, int cols) {
|
public void setFixedSize(short cols, short rows) {
|
||||||
provider.setFixedSize(rows, cols);
|
provider.setFixedSize(cols, rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDynamicSize() {
|
public void setDynamicSize() {
|
||||||
provider.setDyanmicSize();
|
provider.setDyanmicSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumns() {
|
||||||
|
return provider.getColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRows() {
|
||||||
|
return provider.getRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxScrollBackRows(int rows) {
|
||||||
|
provider.setMaxScrollBackRows(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScrollBackRows() {
|
||||||
|
return provider.getScrollBackRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayText() {
|
||||||
|
return getRangeText(0, 0, getColumns(), getRows());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFullText() {
|
||||||
|
return getRangeText(0, -getScrollBackRows(), getColumns(), getRows());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLineText(int line) {
|
||||||
|
return getRangeText(0, line, getColumns(), line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRangeText(int startCol, int startLine, int endCol, int endLine) {
|
||||||
|
return provider.getRangeText(startCol, startLine, endCol, endLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCursorColumn() {
|
||||||
|
return provider.getCursorColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCursorRow() {
|
||||||
|
return provider.getCursorRow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.*;
|
import java.nio.charset.*;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.terminal.vt.VtHandler.KeyMode;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,24 +46,32 @@ public abstract class TerminalAwtEventEncoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final byte ESC = (byte) 0x1b;
|
public static final byte ESC = (byte) 0x1b;
|
||||||
public static final byte FUNC = (byte) 0x4f;
|
|
||||||
|
|
||||||
public static final byte[] CODE_INSERT = vtseq(2);
|
public static final byte[] CODE_INSERT = vtseq(2);
|
||||||
public static final byte[] CODE_DELETE = vtseq(3);
|
public static final byte[] CODE_DELETE = vtseq(3);
|
||||||
public static final byte[] CODE_HOME = { ESC, '[', 'H' };
|
// Believe it or not, \r is ENTER on both Windows and Linux!
|
||||||
public static final byte[] CODE_END = { ESC, '[', 'F' };
|
public static final byte[] CODE_ENTER = { '\r' };
|
||||||
public static final byte[] CODE_PAGE_UP = vtseq(5);
|
public static final byte[] CODE_PAGE_UP = vtseq(5);
|
||||||
public static final byte[] CODE_PAGE_DOWN = vtseq(6);
|
public static final byte[] CODE_PAGE_DOWN = vtseq(6);
|
||||||
public static final byte[] CODE_NUMPAD5 = { ESC, '[', 'E' };
|
public static final byte[] CODE_NUMPAD5 = { ESC, '[', 'E' };
|
||||||
|
|
||||||
public static final byte[] CODE_UP = { ESC, FUNC, 'A' };
|
public static final byte[] CODE_UP_NORMAL = { ESC, '[', 'A' };
|
||||||
public static final byte[] CODE_DOWN = { ESC, FUNC, 'B' };
|
public static final byte[] CODE_DOWN_NORMAL = { ESC, '[', 'B' };
|
||||||
public static final byte[] CODE_RIGHT = { ESC, FUNC, 'C' };
|
public static final byte[] CODE_RIGHT_NORMAL = { ESC, '[', 'C' };
|
||||||
public static final byte[] CODE_LEFT = { ESC, FUNC, 'D' };
|
public static final byte[] CODE_LEFT_NORMAL = { ESC, '[', 'D' };
|
||||||
public static final byte[] CODE_F1 = { ESC, FUNC, 'P' };
|
public static final byte[] CODE_UP_APPLICATION = { ESC, 'O', 'A' };
|
||||||
public static final byte[] CODE_F2 = { ESC, FUNC, 'Q' };
|
public static final byte[] CODE_DOWN_APPLICATION = { ESC, 'O', 'B' };
|
||||||
public static final byte[] CODE_F3 = { ESC, FUNC, 'R' };
|
public static final byte[] CODE_RIGHT_APPLICATION = { ESC, 'O', 'C' };
|
||||||
public static final byte[] CODE_F4 = { ESC, FUNC, 'S' };
|
public static final byte[] CODE_LEFT_APPLICATION = { ESC, 'O', 'D' };
|
||||||
|
public static final byte[] CODE_HOME_NORMAL = { ESC, '[', 'H' };
|
||||||
|
public static final byte[] CODE_END_NORMAL = { ESC, '[', 'F' };
|
||||||
|
public static final byte[] CODE_HOME_APPLICATION = { ESC, 'O', 'H' };
|
||||||
|
public static final byte[] CODE_END_APPLICATION = { ESC, 'O', 'F' };
|
||||||
|
|
||||||
|
public static final byte[] CODE_F1 = { ESC, '[', '1', 'P' };
|
||||||
|
public static final byte[] CODE_F2 = { ESC, '[', '1', 'Q' };
|
||||||
|
public static final byte[] CODE_F3 = { ESC, '[', '1', 'R' };
|
||||||
|
public static final byte[] CODE_F4 = { ESC, '[', '1', 'S' };
|
||||||
public static final byte[] CODE_F5 = vtseq(15);
|
public static final byte[] CODE_F5 = vtseq(15);
|
||||||
public static final byte[] CODE_F6 = vtseq(17);
|
public static final byte[] CODE_F6 = vtseq(17);
|
||||||
public static final byte[] CODE_F7 = vtseq(18);
|
public static final byte[] CODE_F7 = vtseq(18);
|
||||||
|
@ -155,22 +164,23 @@ public abstract class TerminalAwtEventEncoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] getAnsiKeyCode(KeyEvent e) {
|
protected byte[] getAnsiKeyCode(KeyEvent e, KeyMode cursorMode, KeyMode keypadMode) {
|
||||||
if (e.getModifiersEx() != 0) {
|
if (e.getModifiersEx() != 0) {
|
||||||
return getModifiedAnsiKeyCode(e);
|
return getModifiedAnsiKeyCode(e);
|
||||||
}
|
}
|
||||||
return switch (e.getKeyCode()) {
|
return switch (e.getKeyCode()) {
|
||||||
case KeyEvent.VK_INSERT -> CODE_INSERT;
|
case KeyEvent.VK_INSERT -> CODE_INSERT;
|
||||||
// NB. CODE_DELETE is handled in keyTyped
|
// NB. CODE_DELETE is handled in keyTyped
|
||||||
case KeyEvent.VK_HOME -> CODE_HOME;
|
// Yes, HOME and END are considered CURSOR keys
|
||||||
case KeyEvent.VK_END -> CODE_END;
|
case KeyEvent.VK_HOME -> cursorMode.choose(CODE_HOME_NORMAL, CODE_HOME_APPLICATION);
|
||||||
|
case KeyEvent.VK_END -> cursorMode.choose(CODE_END_NORMAL, CODE_END_APPLICATION);
|
||||||
case KeyEvent.VK_PAGE_UP -> CODE_PAGE_UP;
|
case KeyEvent.VK_PAGE_UP -> CODE_PAGE_UP;
|
||||||
case KeyEvent.VK_PAGE_DOWN -> CODE_PAGE_DOWN;
|
case KeyEvent.VK_PAGE_DOWN -> CODE_PAGE_DOWN;
|
||||||
case KeyEvent.VK_NUMPAD5 -> CODE_NUMPAD5;
|
case KeyEvent.VK_NUMPAD5 -> CODE_NUMPAD5;
|
||||||
case KeyEvent.VK_UP -> CODE_UP;
|
case KeyEvent.VK_UP -> cursorMode.choose(CODE_UP_NORMAL, CODE_UP_APPLICATION);
|
||||||
case KeyEvent.VK_DOWN -> CODE_DOWN;
|
case KeyEvent.VK_DOWN -> cursorMode.choose(CODE_DOWN_NORMAL, CODE_DOWN_APPLICATION);
|
||||||
case KeyEvent.VK_RIGHT -> CODE_RIGHT;
|
case KeyEvent.VK_RIGHT -> cursorMode.choose(CODE_RIGHT_NORMAL, CODE_RIGHT_APPLICATION);
|
||||||
case KeyEvent.VK_LEFT -> CODE_LEFT;
|
case KeyEvent.VK_LEFT -> cursorMode.choose(CODE_LEFT_NORMAL, CODE_LEFT_APPLICATION);
|
||||||
case KeyEvent.VK_F1 -> CODE_F1;
|
case KeyEvent.VK_F1 -> CODE_F1;
|
||||||
case KeyEvent.VK_F2 -> CODE_F2;
|
case KeyEvent.VK_F2 -> CODE_F2;
|
||||||
case KeyEvent.VK_F3 -> CODE_F3;
|
case KeyEvent.VK_F3 -> CODE_F3;
|
||||||
|
@ -196,8 +206,8 @@ public abstract class TerminalAwtEventEncoder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e, KeyMode cursorKeyMode, KeyMode keypadMode) {
|
||||||
byte[] bytes = getAnsiKeyCode(e);
|
byte[] bytes = getAnsiKeyCode(e, cursorKeyMode, keypadMode);
|
||||||
bb.put(bytes);
|
bb.put(bytes);
|
||||||
generateBytesExc();
|
generateBytesExc();
|
||||||
}
|
}
|
||||||
|
@ -278,6 +288,10 @@ public abstract class TerminalAwtEventEncoder {
|
||||||
|
|
||||||
public void sendChar(char c) {
|
public void sendChar(char c) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
|
case 0x0a:
|
||||||
|
bb.put(CODE_ENTER);
|
||||||
|
generateBytesExc();
|
||||||
|
break;
|
||||||
case 0x7f:
|
case 0x7f:
|
||||||
bb.put(CODE_DELETE);
|
bb.put(CODE_DELETE);
|
||||||
generateBytesExc();
|
generateBytesExc();
|
||||||
|
@ -296,7 +310,9 @@ public abstract class TerminalAwtEventEncoder {
|
||||||
protected void generateBytesExc() {
|
protected void generateBytesExc() {
|
||||||
bb.flip();
|
bb.flip();
|
||||||
try {
|
try {
|
||||||
generateBytes(bb);
|
if (bb.hasRemaining()) {
|
||||||
|
generateBytes(bb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
Msg.error(this, "Error generating bytes: " + t, t);
|
Msg.error(this, "Error generating bytes: " + t, t);
|
||||||
|
|
|
@ -87,10 +87,13 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
protected VtBuffer buffer = bufPrimary;
|
protected VtBuffer buffer = bufPrimary;
|
||||||
|
|
||||||
// Flags for what's been enabled
|
// Flags for what's been enabled
|
||||||
|
protected boolean showCursor;
|
||||||
protected boolean bracketedPaste;
|
protected boolean bracketedPaste;
|
||||||
protected boolean reportMousePress;
|
protected boolean reportMousePress;
|
||||||
protected boolean reportMouseRelease;
|
protected boolean reportMouseRelease;
|
||||||
protected boolean reportFocus;
|
protected boolean reportFocus;
|
||||||
|
protected KeyMode cursorKeyMode = KeyMode.NORMAL;
|
||||||
|
protected KeyMode keypadMode = KeyMode.NORMAL;
|
||||||
|
|
||||||
private Object lock = new Object();
|
private Object lock = new Object();
|
||||||
|
|
||||||
|
@ -133,6 +136,8 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
reportMousePress = false;
|
reportMousePress = false;
|
||||||
reportMouseRelease = false;
|
reportMouseRelease = false;
|
||||||
reportFocus = false;
|
reportFocus = false;
|
||||||
|
cursorKeyMode = KeyMode.NORMAL;
|
||||||
|
keypadMode = KeyMode.NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processInput(ByteBuffer buffer) {
|
public void processInput(ByteBuffer buffer) {
|
||||||
|
@ -275,7 +280,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
try {
|
try {
|
||||||
// A little strange using both unicode and vt charsets....
|
// A little strange using both unicode and vt charsets....
|
||||||
buffer.putChar(curVtCharset.mapChar(cb.get()));
|
buffer.putChar(curVtCharset.mapChar(cb.get()));
|
||||||
buffer.moveCursorRight(1);
|
buffer.moveCursorRight(1, true, showCursor);
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
Msg.error(this, "Error handling character: " + t, t);
|
Msg.error(this, "Error handling character: " + t, t);
|
||||||
|
@ -291,7 +296,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleBackSpace() {
|
public void handleBackSpace() {
|
||||||
buffer.moveCursorLeft(1);
|
buffer.moveCursorLeft(1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -308,7 +313,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleLineFeed() {
|
public void handleLineFeed() {
|
||||||
buffer.moveCursorDown(1);
|
buffer.moveCursorDown(1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -392,15 +397,17 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleApplicationCursorKeys(boolean en) {
|
public void handleCursorKeyMode(KeyMode mode) {
|
||||||
// Not sure what this means. Ignore for now.
|
this.cursorKeyMode = mode;
|
||||||
Msg.trace(this, "TODO: handleApplicationCursorKeys: " + en);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleApplicationKeypad(boolean en) {
|
public void handleKeypadMode(KeyMode mode) {
|
||||||
// Not sure what this means. Ignore for now.
|
/**
|
||||||
Msg.trace(this, "TODO: handleApplicationKeypad: " + en);
|
* This will be difficult to implement in Swing/AWT, since the OS and Java will already have
|
||||||
|
* mapped the key, including incorporating the NUMLOCK state. Ignore until it matters.
|
||||||
|
*/
|
||||||
|
Msg.trace(this, "TODO: handleKeypadMode: " + mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -417,6 +424,11 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleShowCursor(boolean show) {
|
public void handleShowCursor(boolean show) {
|
||||||
|
this.showCursor = show;
|
||||||
|
if (show) {
|
||||||
|
bufPrimary.checkVerticalScroll();
|
||||||
|
bufAlternate.checkVerticalScroll();
|
||||||
|
}
|
||||||
panel.fieldPanel.setCursorOn(show);
|
panel.fieldPanel.setCursorOn(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,13 +482,13 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
buffer.moveCursorUp(n);
|
buffer.moveCursorUp(n);
|
||||||
return;
|
return;
|
||||||
case DOWN:
|
case DOWN:
|
||||||
buffer.moveCursorDown(n);
|
buffer.moveCursorDown(n, false);
|
||||||
return;
|
return;
|
||||||
case FORWARD:
|
case FORWARD:
|
||||||
buffer.moveCursorRight(n);
|
buffer.moveCursorRight(n, false, showCursor);
|
||||||
return;
|
return;
|
||||||
case BACK:
|
case BACK:
|
||||||
buffer.moveCursorLeft(n);
|
buffer.moveCursorLeft(n, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -603,6 +615,10 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
return buffer.getCurX();
|
return buffer.getCurX();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int resetCursorBottom() {
|
||||||
|
return buffer.resetBottomY();
|
||||||
|
}
|
||||||
|
|
||||||
public int getCols() {
|
public int getCols() {
|
||||||
return buffer.getCols();
|
return buffer.getCols();
|
||||||
}
|
}
|
||||||
|
@ -630,4 +646,8 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
|
||||||
layoutCache.clear();
|
layoutCache.clear();
|
||||||
buildLayouts();
|
buildLayouts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMaxScrollBackSize(int rows) {
|
||||||
|
bufPrimary.setMaxScrollBack(rows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ public interface TerminalListener {
|
||||||
* @param cols the number of columns
|
* @param cols the number of columns
|
||||||
* @param rows the number of rows
|
* @param rows the number of rows
|
||||||
*/
|
*/
|
||||||
default void resized(int cols, int rows) {
|
default void resized(short cols, short rows) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -209,7 +209,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
if (provider.isLocalActionKeyBinding(e)) {
|
if (provider.isLocalActionKeyBinding(e)) {
|
||||||
return; // Do not consume, so action can take it
|
return; // Do not consume, so action can take it
|
||||||
}
|
}
|
||||||
eventEncoder.keyPressed(e);
|
eventEncoder.keyPressed(e, model.cursorKeyMode, model.keypadMode);
|
||||||
e.consume();
|
e.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
terminalListeners.remove(listener);
|
terminalListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void notifyTerminalResized(int cols, int rows) {
|
protected void notifyTerminalResized(short cols, short rows) {
|
||||||
for (TerminalListener l : terminalListeners) {
|
for (TerminalListener l : terminalListeners) {
|
||||||
try {
|
try {
|
||||||
l.resized(cols, rows);
|
l.resized(cols, rows);
|
||||||
|
@ -530,7 +530,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
fieldPanel.setCursorPosition(BigInteger.valueOf(model.getCursorRow() + scrollBack), 0, 0,
|
fieldPanel.setCursorPosition(BigInteger.valueOf(model.getCursorRow() + scrollBack), 0, 0,
|
||||||
model.getCursorColumn());
|
model.getCursorColumn());
|
||||||
if (scroll) {
|
if (scroll) {
|
||||||
fieldPanel.scrollToCursor();
|
fieldPanel.scrollTo(new FieldLocation(model.resetCursorBottom() + scrollBack));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,14 +664,14 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
|
|
||||||
protected void resizeTerminalToWindow() {
|
protected void resizeTerminalToWindow() {
|
||||||
Rectangle bounds = scroller.getViewportBorderBounds();
|
Rectangle bounds = scroller.getViewportBorderBounds();
|
||||||
int rows = bounds.height / metrics.getHeight();
|
|
||||||
int cols = bounds.width / metrics.charWidth('M');
|
int cols = bounds.width / metrics.charWidth('M');
|
||||||
resizeTerminal(rows, cols);
|
int rows = bounds.height / metrics.getHeight();
|
||||||
|
resizeTerminal((short) cols, (short) rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resizeTerminal(int rows, int cols) {
|
protected void resizeTerminal(short cols, short rows) {
|
||||||
if (model.resizeTerminal(cols, rows)) {
|
if (model.resizeTerminal(Short.toUnsignedInt(cols), Short.toUnsignedInt(rows))) {
|
||||||
notifyTerminalResized(model.getCols(), model.getRows());
|
notifyTerminalResized((short) model.getCols(), (short) model.getRows());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,14 +683,14 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
* needed. If the terminal size changes as a result of this call,
|
* needed. If the terminal size changes as a result of this call,
|
||||||
* {@link TerminalListener#resized(int, int)} is invoked.
|
* {@link TerminalListener#resized(int, int)} is invoked.
|
||||||
*
|
*
|
||||||
* @param rows the number of rows
|
|
||||||
* @param cols the number of columns
|
* @param cols the number of columns
|
||||||
|
* @param rows the number of rows
|
||||||
*/
|
*/
|
||||||
public void setFixedTerminalSize(int rows, int cols) {
|
public void setFixedTerminalSize(short cols, short rows) {
|
||||||
this.fixedSize = true;
|
this.fixedSize = true;
|
||||||
scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||||
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
resizeTerminal(rows, cols);
|
resizeTerminal(cols, rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -709,4 +709,20 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
resizeTerminalToWindow();
|
resizeTerminalToWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getColumns() {
|
||||||
|
return model.getCols();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() {
|
||||||
|
return model.getRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorColumn() {
|
||||||
|
return model.getCursorColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorRow() {
|
||||||
|
return model.getCursorRow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.app.services.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The plugin that provides {@link TerminalService}
|
* The plugin that provides {@link TerminalService}
|
||||||
|
@ -52,13 +53,15 @@ public class TerminalPlugin extends Plugin implements TerminalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TerminalProvider createProvider(Charset charset, VtOutput outputCb) {
|
public TerminalProvider createProvider(Charset charset, VtOutput outputCb) {
|
||||||
TerminalProvider provider = new TerminalProvider(this, charset);
|
return Swing.runNow(() -> {
|
||||||
provider.setOutputCallback(outputCb);
|
TerminalProvider provider = new TerminalProvider(this, charset);
|
||||||
provider.addToTool();
|
provider.setOutputCallback(outputCb);
|
||||||
provider.setVisible(true);
|
provider.addToTool();
|
||||||
providers.add(provider);
|
provider.setVisible(true);
|
||||||
provider.setClipboardService(clipboardService);
|
providers.add(provider);
|
||||||
return provider;
|
provider.setClipboardService(clipboardService);
|
||||||
|
return provider;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,6 +75,7 @@ public class TerminalPlugin extends Plugin implements TerminalService {
|
||||||
return new ThreadedTerminal(createProvider(charset, buf -> {
|
return new ThreadedTerminal(createProvider(charset, buf -> {
|
||||||
while (buf.hasRemaining()) {
|
while (buf.hasRemaining()) {
|
||||||
try {
|
try {
|
||||||
|
//ThreadedTerminal.printBuffer(">> ", buf);
|
||||||
channel.write(buf);
|
channel.write(buf);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
|
|
@ -299,11 +299,42 @@ public class TerminalProvider extends ComponentProviderAdapter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFixedSize(int rows, int cols) {
|
public void setFixedSize(short cols, short rows) {
|
||||||
panel.setFixedTerminalSize(rows, cols);
|
panel.setFixedTerminalSize(cols, rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDyanmicSize() {
|
public void setDyanmicSize() {
|
||||||
panel.setDynamicTerminalSize();
|
panel.setDynamicTerminalSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getColumns() {
|
||||||
|
return panel.getColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() {
|
||||||
|
return panel.getRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxScrollBackRows(int rows) {
|
||||||
|
panel.model.setMaxScrollBackSize(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScrollBackRows() {
|
||||||
|
return panel.model.getScrollBackSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRangeText(int startCol, int startLine, int endCol, int endLine) {
|
||||||
|
int scrollBack = getScrollBackRows();
|
||||||
|
return panel.getSelectedText(new FieldRange(
|
||||||
|
new FieldLocation(startLine + scrollBack, 0, 0, startCol),
|
||||||
|
new FieldLocation(endLine + scrollBack, 0, 0, endCol)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorColumn() {
|
||||||
|
return panel.getCursorColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorRow() {
|
||||||
|
return panel.getCursorRow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,8 @@ public class TerminalTextField implements TextField {
|
||||||
g.setColor(cursorColor);
|
g.setColor(cursorColor);
|
||||||
int x = startX + findX(cursorLoc.col());
|
int x = startX + findX(cursorLoc.col());
|
||||||
g.drawRect(x, -getHeightAbove(), em - 1, getHeight() - 1);
|
g.drawRect(x, -getHeightAbove(), em - 1, getHeight() - 1);
|
||||||
g.drawRect(x + 1, -getHeightAbove() + 1, em - 3, getHeight() - 3);
|
// This technique looks ugly with display scaling
|
||||||
|
//g.drawRect(x + 1, -getHeightAbove() + 1, em - 3, getHeight() - 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,11 +61,10 @@ public class ThreadedTerminal extends DefaultTerminal {
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused") // diagnostic
|
static void printBuffer(String prefix, ByteBuffer bb) {
|
||||||
private void printBuffer() {
|
byte[] bytes = new byte[bb.remaining()];
|
||||||
byte[] bytes = new byte[buffer.remaining()];
|
bb.get(bb.position(), bytes);
|
||||||
buffer.get(buffer.position(), bytes);
|
System.err.print(prefix);
|
||||||
//System.err.println("<< " + NumericUtilities.convertBytesToString(bytes, ":"));
|
|
||||||
try {
|
try {
|
||||||
String str = new String(bytes, "US-ASCII");
|
String str = new String(bytes, "US-ASCII");
|
||||||
for (char c : str.toCharArray()) {
|
for (char c : str.toCharArray()) {
|
||||||
|
@ -93,7 +92,7 @@ public class ThreadedTerminal extends DefaultTerminal {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
//printBuffer();
|
//printBuffer("<< ", buffer);
|
||||||
synchronized (buffer) {
|
synchronized (buffer) {
|
||||||
provider.processInput(buffer);
|
provider.processInput(buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ public class VtBuffer {
|
||||||
protected int curY;
|
protected int curY;
|
||||||
protected int savedX;
|
protected int savedX;
|
||||||
protected int savedY;
|
protected int savedY;
|
||||||
|
protected int bottomY; // for scrolling UI after an update
|
||||||
protected int scrollStart;
|
protected int scrollStart;
|
||||||
protected int scrollEnd; // exclusive
|
protected int scrollEnd; // exclusive
|
||||||
|
|
||||||
|
@ -128,6 +129,8 @@ public class VtBuffer {
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
checkVerticalScroll();
|
||||||
|
// At this point, we have no choice but to wrap
|
||||||
lines.get(curY).putChar(curX, c, curAttrs);
|
lines.get(curY).putChar(curX, c, curAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +139,7 @@ public class VtBuffer {
|
||||||
*/
|
*/
|
||||||
public void tab() {
|
public void tab() {
|
||||||
int n = TAB_WIDTH + (-curX % TAB_WIDTH);
|
int n = TAB_WIDTH + (-curX % TAB_WIDTH);
|
||||||
moveCursorRight(n);
|
moveCursorRight(n, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,7 +150,7 @@ public class VtBuffer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int n = (curX - 1) % TAB_WIDTH + 1;
|
int n = (curX - 1) % TAB_WIDTH + 1;
|
||||||
moveCursorLeft(n);
|
moveCursorLeft(n, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,6 +160,13 @@ public class VtBuffer {
|
||||||
* This does <em>not</em> move the cursor down.
|
* This does <em>not</em> move the cursor down.
|
||||||
*/
|
*/
|
||||||
public void carriageReturn() {
|
public void carriageReturn() {
|
||||||
|
if (curX == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int prevY = curY - 1;
|
||||||
|
if (prevY >= 0 && prevY < lines.size()) {
|
||||||
|
lines.get(prevY).wrappedToNext = false;
|
||||||
|
}
|
||||||
curX = 0;
|
curX = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,57 +246,82 @@ public class VtBuffer {
|
||||||
* that the cursor remains in the display. The value of n must be positive, otherwise behavior
|
* that the cursor remains in the display. The value of n must be positive, otherwise behavior
|
||||||
* is undefined. To move the cursor up, use {@link #moveCursorUp(int)}.
|
* is undefined. To move the cursor up, use {@link #moveCursorUp(int)}.
|
||||||
*
|
*
|
||||||
* @param n
|
* <p>
|
||||||
|
* ConPty has a habit of moving the cursor past the end of the current line before sending CRLF.
|
||||||
|
* (Though, I imagine there are other applications that might do this.) The {@code dedupWrap}
|
||||||
|
* parameter is made to accommodate this. If it is set, n is a single line, and the previous
|
||||||
|
* line was wrapped, then this does nothing more than remove the wrapped flag from the previous
|
||||||
|
* line.
|
||||||
|
*
|
||||||
|
* @param n the number of lines to move down
|
||||||
|
* @param dedupWrap whether to detect and ignore a line feed after wrapping
|
||||||
*/
|
*/
|
||||||
public void moveCursorDown(int n) {
|
public void moveCursorDown(int n, boolean dedupWrap) {
|
||||||
curY += n;
|
int prevY = curY - 1;
|
||||||
checkVerticalScroll();
|
if (dedupWrap && n == 1 && prevY >= 0 && prevY < lines.size() &&
|
||||||
|
lines.get(prevY).wrappedToNext) {
|
||||||
|
lines.get(prevY).wrappedToNext = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curY += n;
|
||||||
|
bottomY = Math.max(bottomY, curY);
|
||||||
|
checkVerticalScroll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the cursor left (backward) n columns
|
* Move the cursor left (backward) n columns
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If the cursor would move left of the display, it is instead moved to the far right of the
|
* The cursor is clamped into the display. If wrap is specified, the cursor would exceed the
|
||||||
* previous row, unless the cursor is already on the top row, in which case, it will be placed
|
* left side of the display, and the previous line was wrapped onto the current line, then the
|
||||||
* in the top-left corner of the display. NOTE: If the cursor is moved to the previous row, no
|
* cursor will instead be moved to the end of the previous line. (It doesn't matter how far the
|
||||||
* heed is given to "leftovers." It doesn't matter how far to the left the cursor would have
|
* cursor would exceed the left; it moves up at most one line.) The value of n must be positive,
|
||||||
* been; it is moved to the far right column and exactly one row up. The value of n must be
|
* otherwise behavior is undefined. To move the cursor right, use {@link #moveCursorRight(int)}.
|
||||||
* positive, otherwise behavior is undefined. To move the cursor right, use
|
|
||||||
* {@link #moveCursorRight(int)}.
|
|
||||||
*
|
*
|
||||||
* @param n the number of columns
|
* @param n the number of columns
|
||||||
|
* @param wrap whether to wrap the cursor to the previous line if would exceed the left of the
|
||||||
|
* display
|
||||||
*/
|
*/
|
||||||
public void moveCursorLeft(int n) {
|
public void moveCursorLeft(int n, boolean wrap) {
|
||||||
if (curX - n >= 0) {
|
int prevY = curY - 1;
|
||||||
curX -= n;
|
if (wrap && curX - n < 0 && prevY >= 0 && prevY < lines.size() &&
|
||||||
}
|
lines.get(prevY).wrappedToNext) {
|
||||||
else if (curY > 0) {
|
|
||||||
curX = cols - 1;
|
curX = cols - 1;
|
||||||
curY--;
|
curY--;
|
||||||
|
lines.get(curY).wrappedToNext = false;
|
||||||
}
|
}
|
||||||
|
curX = Math.max(0, Math.min(curX - n, cols - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the cursor right (forward) n columns
|
* Move the cursor right (forward) n columns
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If the cursor would move right of the display, it is instead moved to the far left of the
|
* The cursor is clamped into the display. If wrap is specified and the cursor would exceed the
|
||||||
* next row. If the cursor is already on the bottom row, the viewport is scrolled down a line.
|
* right side of the display, the cursor will instead be wrapped to the start of the next line,
|
||||||
* NOTE: If the cursor is moved to the next row, no heed is given to "leftovers." It doesn't
|
* possibly scrolling the viewport down. (It doesn't matter how far the cursor exceeds the
|
||||||
* matter how far to the right the cursor would have been; it is moved to the far left column
|
* right; the cursor moves down exactly one line.) The value of n must be positive, otherwise
|
||||||
* and exactly one row down. The value of n must be positive, otherwise behavior is undefined.
|
* behavior is undefined. To move the cursor left, use {@link #moveCursorLeft(int)}.
|
||||||
* To move the cursor left, use {@link #moveCursorLeft(int)}.
|
|
||||||
*
|
*
|
||||||
* @param n the number of columns
|
* @param n the number of columns
|
||||||
|
* @param wrap whether to wrap the cursor to the next line if it would exceed the right of the
|
||||||
|
* display
|
||||||
*/
|
*/
|
||||||
public void moveCursorRight(int n) {
|
public void moveCursorRight(int n, boolean wrap, boolean isCursorShowing) {
|
||||||
curX += n;
|
if (wrap && curX + n >= cols) {
|
||||||
if (curX >= cols) {
|
checkVerticalScroll();
|
||||||
curX = 0;
|
curX = 0;
|
||||||
|
lines.get(curY).wrappedToNext = true;
|
||||||
curY++;
|
curY++;
|
||||||
|
bottomY = Math.max(bottomY, curY);
|
||||||
|
if (isCursorShowing) {
|
||||||
|
checkVerticalScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curX = Math.max(0, Math.min(curX + n, cols - 1));
|
||||||
}
|
}
|
||||||
checkVerticalScroll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -313,6 +348,7 @@ public class VtBuffer {
|
||||||
public void restoreCursorPos() {
|
public void restoreCursorPos() {
|
||||||
curX = savedX;
|
curX = savedX;
|
||||||
curY = savedY;
|
curY = savedY;
|
||||||
|
bottomY = Math.max(bottomY, curY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -326,8 +362,9 @@ public class VtBuffer {
|
||||||
* @param col the desired column, 0 up, left to right
|
* @param col the desired column, 0 up, left to right
|
||||||
*/
|
*/
|
||||||
public void moveCursor(int row, int col) {
|
public void moveCursor(int row, int col) {
|
||||||
this.curX = Math.max(0, Math.min(cols - 1, col));
|
curX = Math.max(0, Math.min(cols - 1, col));
|
||||||
this.curY = Math.max(0, Math.min(rows - 1, row));
|
curY = Math.max(0, Math.min(rows - 1, row));
|
||||||
|
bottomY = Math.max(bottomY, curY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -370,6 +407,9 @@ public class VtBuffer {
|
||||||
public void erase(Erasure erasure) {
|
public void erase(Erasure erasure) {
|
||||||
switch (erasure) {
|
switch (erasure) {
|
||||||
case TO_DISPLAY_END:
|
case TO_DISPLAY_END:
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int y = curY; y < rows; y++) {
|
for (int y = curY; y < rows; y++) {
|
||||||
VtLine line = lines.get(y);
|
VtLine line = lines.get(y);
|
||||||
if (y == curY) {
|
if (y == curY) {
|
||||||
|
@ -403,12 +443,21 @@ public class VtBuffer {
|
||||||
scrollBack.clear();
|
scrollBack.clear();
|
||||||
return;
|
return;
|
||||||
case TO_LINE_END:
|
case TO_LINE_END:
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lines.get(curY).clearToEnd(curX);
|
lines.get(curY).clearToEnd(curX);
|
||||||
return;
|
return;
|
||||||
case TO_LINE_START:
|
case TO_LINE_START:
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lines.get(curY).clearToStart(curX, curAttrs);
|
lines.get(curY).clearToStart(curX, curAttrs);
|
||||||
return;
|
return;
|
||||||
case FULL_LINE:
|
case FULL_LINE:
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lines.get(curY).clear();
|
lines.get(curY).clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -462,6 +511,9 @@ public class VtBuffer {
|
||||||
* @param n the number of blanks to insert.
|
* @param n the number of blanks to insert.
|
||||||
*/
|
*/
|
||||||
public void insertChars(int n) {
|
public void insertChars(int n) {
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lines.get(curY).insert(curX, n);
|
lines.get(curY).insert(curX, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,6 +527,9 @@ public class VtBuffer {
|
||||||
* @param n the number of characters to delete
|
* @param n the number of characters to delete
|
||||||
*/
|
*/
|
||||||
public void deleteChars(int n) {
|
public void deleteChars(int n) {
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lines.get(curY).delete(curX, curX + n);
|
lines.get(curY).delete(curX, curX + n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,6 +543,9 @@ public class VtBuffer {
|
||||||
* @param n the number of characters to erase
|
* @param n the number of characters to erase
|
||||||
*/
|
*/
|
||||||
public void eraseChars(int n) {
|
public void eraseChars(int n) {
|
||||||
|
if (curY >= lines.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lines.get(curY).erase(curX, curX + n, curAttrs);
|
lines.get(curY).erase(curX, curX + n, curAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,4 +808,10 @@ public class VtBuffer {
|
||||||
}
|
}
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int resetBottomY() {
|
||||||
|
int ret = bottomY;
|
||||||
|
bottomY = curY;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,7 @@ public interface VtHandler {
|
||||||
public static final byte[] Q7 = ascii("?7");
|
public static final byte[] Q7 = ascii("?7");
|
||||||
public static final byte[] Q12 = ascii("?12");
|
public static final byte[] Q12 = ascii("?12");
|
||||||
public static final byte[] Q25 = ascii("?25");
|
public static final byte[] Q25 = ascii("?25");
|
||||||
|
public static final byte[] Q47 = ascii("?47");
|
||||||
public static final byte[] Q1000 = ascii("?1000");
|
public static final byte[] Q1000 = ascii("?1000");
|
||||||
public static final byte[] Q1004 = ascii("?1004");
|
public static final byte[] Q1004 = ascii("?1004");
|
||||||
public static final byte[] Q1034 = ascii("?1034");
|
public static final byte[] Q1034 = ascii("?1034");
|
||||||
|
@ -502,6 +503,29 @@ public interface VtHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For cursor and keypad, specifies normal or application mode
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This affects the codes sent by the terminal.
|
||||||
|
*/
|
||||||
|
public enum KeyMode {
|
||||||
|
NORMAL {
|
||||||
|
@Override
|
||||||
|
public <T> T choose(T normal, T application) {
|
||||||
|
return normal;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
APPLICATION {
|
||||||
|
@Override
|
||||||
|
public <T> T choose(T normal, T application) {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract <T> T choose(T normal, T application);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given buffer's contents are equal to that of the given array
|
* Check if the given buffer's contents are equal to that of the given array
|
||||||
*
|
*
|
||||||
|
@ -663,7 +687,7 @@ public interface VtHandler {
|
||||||
handleInsertMode(en);
|
handleInsertMode(en);
|
||||||
}
|
}
|
||||||
else if (bufEq(csiParam, Q1)) {
|
else if (bufEq(csiParam, Q1)) {
|
||||||
handleApplicationCursorKeys(en);
|
handleCursorKeyMode(en ? KeyMode.APPLICATION : KeyMode.NORMAL);
|
||||||
}
|
}
|
||||||
else if (bufEq(csiParam, Q7)) {
|
else if (bufEq(csiParam, Q7)) {
|
||||||
handleAutoWrapMode(en);
|
handleAutoWrapMode(en);
|
||||||
|
@ -674,6 +698,10 @@ public interface VtHandler {
|
||||||
else if (bufEq(csiParam, Q25)) {
|
else if (bufEq(csiParam, Q25)) {
|
||||||
handleShowCursor(en);
|
handleShowCursor(en);
|
||||||
}
|
}
|
||||||
|
else if (bufEq(csiParam, Q47)) {
|
||||||
|
// NB. Same as 1047?
|
||||||
|
handleAltScreenBuffer(en, false);
|
||||||
|
}
|
||||||
else if (bufEq(csiParam, Q1000)) {
|
else if (bufEq(csiParam, Q1000)) {
|
||||||
handleReportMouseEvents(en, en);
|
handleReportMouseEvents(en, en);
|
||||||
}
|
}
|
||||||
|
@ -684,6 +712,7 @@ public interface VtHandler {
|
||||||
handleMetaKey(en);
|
handleMetaKey(en);
|
||||||
}
|
}
|
||||||
else if (bufEq(csiParam, Q1047)) {
|
else if (bufEq(csiParam, Q1047)) {
|
||||||
|
// NB. Same as 47?
|
||||||
handleAltScreenBuffer(en, false);
|
handleAltScreenBuffer(en, false);
|
||||||
}
|
}
|
||||||
else if (bufEq(csiParam, Q1048)) {
|
else if (bufEq(csiParam, Q1048)) {
|
||||||
|
@ -981,6 +1010,11 @@ public interface VtHandler {
|
||||||
// TODO: 104;c;c;c... is color reset. I've not implemented setting them, though.
|
// TODO: 104;c;c;c... is color reset. I've not implemented setting them, though.
|
||||||
// No c given = reset all
|
// No c given = reset all
|
||||||
|
|
||||||
|
// Windows includes the null terminator
|
||||||
|
static String truncateAtNull(String str) {
|
||||||
|
return str.split("\000", 2)[0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an OSC sequence
|
* Handle an OSC sequence
|
||||||
*
|
*
|
||||||
|
@ -994,7 +1028,7 @@ public interface VtHandler {
|
||||||
|
|
||||||
matcher = PAT_OSC_WINDOW_TITLE.matcher(paramStr);
|
matcher = PAT_OSC_WINDOW_TITLE.matcher(paramStr);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
handleWindowTitle(matcher.group("title"));
|
handleWindowTitle(truncateAtNull(matcher.group("title")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1349,18 +1383,18 @@ public interface VtHandler {
|
||||||
void handleInsertMode(boolean en);
|
void handleInsertMode(boolean en);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle application handling of the cursor keys
|
* Toggle cursor key mode
|
||||||
*
|
*
|
||||||
* @param en true (default) for application control, false for local control
|
* @param mode the key mode
|
||||||
*/
|
*/
|
||||||
void handleApplicationCursorKeys(boolean en);
|
void handleCursorKeyMode(KeyMode mode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle application handling of the keypad
|
* Toggle keypad mode
|
||||||
*
|
*
|
||||||
* @param en true for application control, false for local control
|
* @param mode the key mode
|
||||||
*/
|
*/
|
||||||
void handleApplicationKeypad(boolean en);
|
void handleKeypadMode(KeyMode mode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle auto-wrap mode
|
* Toggle auto-wrap mode
|
||||||
|
|
|
@ -22,6 +22,7 @@ public class VtLine {
|
||||||
protected int cols;
|
protected int cols;
|
||||||
protected int len;
|
protected int len;
|
||||||
protected char[] chars;
|
protected char[] chars;
|
||||||
|
protected boolean wrappedToNext;
|
||||||
private VtAttributes[] cellAttrs;
|
private VtAttributes[] cellAttrs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,6 +81,7 @@ public class VtLine {
|
||||||
public void putChar(int x, char c, VtAttributes attrs) {
|
public void putChar(int x, char c, VtAttributes attrs) {
|
||||||
int oldLen = len;
|
int oldLen = len;
|
||||||
len = Math.max(len, x + 1);
|
len = Math.max(len, x + 1);
|
||||||
|
wrappedToNext = false; // Maybe remove
|
||||||
for (int i = oldLen; i < x; i++) {
|
for (int i = oldLen; i < x; i++) {
|
||||||
chars[i] = ' ';
|
chars[i] = ' ';
|
||||||
cellAttrs[i] = VtAttributes.DEFAULTS;
|
cellAttrs[i] = VtAttributes.DEFAULTS;
|
||||||
|
@ -118,6 +120,7 @@ public class VtLine {
|
||||||
public void reset(int cols) {
|
public void reset(int cols) {
|
||||||
this.cols = cols;
|
this.cols = cols;
|
||||||
this.len = 0;
|
this.len = 0;
|
||||||
|
this.wrappedToNext = false;
|
||||||
if (this.cols != cols || this.chars == null) {
|
if (this.cols != cols || this.chars == null) {
|
||||||
this.chars = new char[cols];
|
this.chars = new char[cols];
|
||||||
this.cellAttrs = new VtAttributes[cols];
|
this.cellAttrs = new VtAttributes[cols];
|
||||||
|
@ -147,6 +150,7 @@ public class VtLine {
|
||||||
*/
|
*/
|
||||||
public void clear() {
|
public void clear() {
|
||||||
len = 0;
|
len = 0;
|
||||||
|
wrappedToNext = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,6 +160,7 @@ public class VtLine {
|
||||||
*/
|
*/
|
||||||
public void clearToEnd(int x) {
|
public void clearToEnd(int x) {
|
||||||
len = Math.min(len, x);
|
len = Math.min(len, x);
|
||||||
|
wrappedToNext = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,6 +172,7 @@ public class VtLine {
|
||||||
public void clearToStart(int x, VtAttributes attrs) {
|
public void clearToStart(int x, VtAttributes attrs) {
|
||||||
if (len <= x) {
|
if (len <= x) {
|
||||||
len = 0;
|
len = 0;
|
||||||
|
wrappedToNext = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0; i <= x; i++) {
|
for (int i = 0; i <= x; i++) {
|
||||||
|
@ -183,7 +189,8 @@ public class VtLine {
|
||||||
*/
|
*/
|
||||||
public void delete(int start, int end) {
|
public void delete(int start, int end) {
|
||||||
if (len <= end) {
|
if (len <= end) {
|
||||||
len = start;
|
len = Math.min(len, start);
|
||||||
|
wrappedToNext = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int shift = end - start;
|
int shift = end - start;
|
||||||
|
@ -208,7 +215,8 @@ public class VtLine {
|
||||||
*/
|
*/
|
||||||
public void erase(int start, int end, VtAttributes attrs) {
|
public void erase(int start, int end, VtAttributes attrs) {
|
||||||
if (len <= end) {
|
if (len <= end) {
|
||||||
len = start;
|
len = Math.min(len, start);
|
||||||
|
wrappedToNext = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int x = start; x < end; x++) {
|
for (int x = start; x < end; x++) {
|
||||||
|
@ -238,6 +246,7 @@ public class VtLine {
|
||||||
chars[x] = ' ';
|
chars[x] = ' ';
|
||||||
}
|
}
|
||||||
len = Math.min(cols, len + n);
|
len = Math.min(cols, len + n);
|
||||||
|
wrappedToNext = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.app.plugin.core.terminal.vt;
|
package ghidra.app.plugin.core.terminal.vt;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.terminal.vt.VtCharset.G;
|
import ghidra.app.plugin.core.terminal.vt.VtCharset.G;
|
||||||
|
import ghidra.app.plugin.core.terminal.vt.VtHandler.KeyMode;
|
||||||
|
|
||||||
public enum VtState {
|
public enum VtState {
|
||||||
/**
|
/**
|
||||||
|
@ -61,11 +62,10 @@ public enum VtState {
|
||||||
case ']':
|
case ']':
|
||||||
return OSC_PARAM;
|
return OSC_PARAM;
|
||||||
case '=':
|
case '=':
|
||||||
handler.handleApplicationKeypad(true);
|
handler.handleKeypadMode(KeyMode.APPLICATION);
|
||||||
return CHAR;
|
return CHAR;
|
||||||
case '>':
|
case '>':
|
||||||
// Normal keypad
|
handler.handleKeypadMode(KeyMode.NORMAL);
|
||||||
handler.handleApplicationKeypad(false);
|
|
||||||
return CHAR;
|
return CHAR;
|
||||||
case 'D':
|
case 'D':
|
||||||
handler.handleScrollViewportDown(1, true);
|
handler.handleScrollViewportDown(1, true);
|
||||||
|
@ -267,7 +267,8 @@ public enum VtState {
|
||||||
OSC_PARAM {
|
OSC_PARAM {
|
||||||
@Override
|
@Override
|
||||||
protected VtState handleNext(byte b, VtParser parser, VtHandler handler) {
|
protected VtState handleNext(byte b, VtParser parser, VtHandler handler) {
|
||||||
if (0x20 <= b && b <= 0x7f) {
|
// For whatever reason, Windows includes the null terminator in titles
|
||||||
|
if (0x20 <= b && b <= 0x7f || b == 0) {
|
||||||
parser.oscParam.put(b);
|
parser.oscParam.put(b);
|
||||||
return OSC_PARAM;
|
return OSC_PARAM;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,18 +61,113 @@ public interface Terminal extends AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the terminal size to the given dimensions, as do <em>not</em> resize it to the window.
|
* Set the terminal size to the given dimensions, and do <em>not</em> resize it to the window.
|
||||||
*
|
*
|
||||||
* @param rows the number of rows
|
|
||||||
* @param cols the number of columns
|
* @param cols the number of columns
|
||||||
|
* @param rows the number of rows
|
||||||
*/
|
*/
|
||||||
void setFixedSize(int rows, int cols);
|
void setFixedSize(short cols, short rows);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fit the terminals dimensions to the containing window.
|
* @see #setFixedSize(short, short)
|
||||||
|
*/
|
||||||
|
default void setFixedSize(int cols, int rows) {
|
||||||
|
setFixedSize((short) cols, (short) rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fit the terminal's dimensions to the containing window.
|
||||||
*/
|
*/
|
||||||
void setDynamicSize();
|
void setDynamicSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum size of the scroll-back buffer in lines
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This only affects the primary buffer. The alternate buffer has no scroll-back.
|
||||||
|
*/
|
||||||
|
void setMaxScrollBackRows(int rows);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum number of characters in each row
|
||||||
|
*
|
||||||
|
* @return the column count
|
||||||
|
*/
|
||||||
|
int getColumns();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum number of rows in the display (not counting scroll-back)
|
||||||
|
*
|
||||||
|
* @return the row count
|
||||||
|
*/
|
||||||
|
int getRows();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of lines in the scroll-back buffer
|
||||||
|
*
|
||||||
|
* @return the size of the buffer in lines
|
||||||
|
*/
|
||||||
|
int getScrollBackRows();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the text in the terminal, including the scroll-back buffer
|
||||||
|
*
|
||||||
|
* @return the full text
|
||||||
|
*/
|
||||||
|
String getFullText();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text in the terminal, excluding the scroll-back buffer
|
||||||
|
*
|
||||||
|
* @return the display text
|
||||||
|
*/
|
||||||
|
String getDisplayText();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the given line's text
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The line at the top of the display has index 0. Lines in the scroll-back buffer have negative
|
||||||
|
* indices.
|
||||||
|
*
|
||||||
|
* @param line the index, 0 up
|
||||||
|
* @return the text in the line
|
||||||
|
*/
|
||||||
|
String getLineText(int line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text in the given range
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The line at the top of the display has index 0. Lines in the scroll-back buffer have negative
|
||||||
|
* indices.
|
||||||
|
*
|
||||||
|
* @param startCol the first column to include in the starting line
|
||||||
|
* @param startLine the first line to include
|
||||||
|
* @param endCol the first column to <em>exclude</em> in the ending line
|
||||||
|
* @param endLine the last line to include
|
||||||
|
* @return the text in the given range
|
||||||
|
*/
|
||||||
|
String getRangeText(int startCol, int startLine, int endCol, int endLine);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cursor's current line
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Lines are indexed 0 up where the top line of the display is 0. The cursor can never be in the
|
||||||
|
* scroll-back buffer.
|
||||||
|
*
|
||||||
|
* @return the line, 0 up, top to bottom
|
||||||
|
*/
|
||||||
|
int getCursorRow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cursor's current column
|
||||||
|
*
|
||||||
|
* @return the column, 0 up, left to right
|
||||||
|
*/
|
||||||
|
int getCursorColumn();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void close();
|
void close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import ghidra.pty.windows.ConPtyFactory;
|
||||||
* A mechanism for opening pseudo-terminals
|
* A mechanism for opening pseudo-terminals
|
||||||
*/
|
*/
|
||||||
public interface PtyFactory {
|
public interface PtyFactory {
|
||||||
|
short DEFAULT_COLS = 80;
|
||||||
|
short DEFAULT_ROWS = 25;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose a factory of local pty's for the host operating system
|
* Choose a factory of local pty's for the host operating system
|
||||||
|
@ -35,11 +37,11 @@ public interface PtyFactory {
|
||||||
static PtyFactory local() {
|
static PtyFactory local() {
|
||||||
switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
||||||
case MAC_OS_X:
|
case MAC_OS_X:
|
||||||
return new MacosPtyFactory();
|
return MacosPtyFactory.INSTANCE;
|
||||||
case LINUX:
|
case LINUX:
|
||||||
return new LinuxPtyFactory();
|
return LinuxPtyFactory.INSTANCE;
|
||||||
case WINDOWS:
|
case WINDOWS:
|
||||||
return new ConPtyFactory();
|
return ConPtyFactory.INSTANCE;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
@ -48,10 +50,40 @@ public interface PtyFactory {
|
||||||
/**
|
/**
|
||||||
* Open a new pseudo-terminal
|
* Open a new pseudo-terminal
|
||||||
*
|
*
|
||||||
|
* @param cols the initial width in characters, or 0 to let the system decide both dimensions
|
||||||
|
* @param rows the initial height in characters, or 0 to let the system decide both dimensions
|
||||||
* @return new new Pty
|
* @return new new Pty
|
||||||
* @throws IOException for an I/O error, including cancellation
|
* @throws IOException for an I/O error, including cancellation
|
||||||
*/
|
*/
|
||||||
Pty openpty() throws IOException;
|
Pty openpty(short cols, short rows) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new pseudo-terminal of the default size ({@value #DEFAULT_COLS} x
|
||||||
|
* {@value #DEFAULT_ROWS})
|
||||||
|
*
|
||||||
|
* @return new new Pty
|
||||||
|
* @throws IOException for an I/O error, including cancellation
|
||||||
|
*/
|
||||||
|
default Pty openpty() throws IOException {
|
||||||
|
return openpty(DEFAULT_COLS, DEFAULT_ROWS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new pseudo-terminal
|
||||||
|
*
|
||||||
|
* @param cols the initial width in characters, or 0 to let the system decide both dimensions
|
||||||
|
* @param rows the initial height in characters, or 0 to let the system decide both dimensions
|
||||||
|
* @return new new Pty
|
||||||
|
* @throws IOException for an I/O error, including cancellation
|
||||||
|
*/
|
||||||
|
default Pty openpty(int cols, int rows) throws IOException {
|
||||||
|
return openpty((short) cols, (short) rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a human-readable description of the factory
|
||||||
|
*
|
||||||
|
* @return the description
|
||||||
|
*/
|
||||||
String getDescription();
|
String getDescription();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,11 @@ package ghidra.pty;
|
||||||
* The parent (UNIX "master") end of a pseudo-terminal
|
* The parent (UNIX "master") end of a pseudo-terminal
|
||||||
*/
|
*/
|
||||||
public interface PtyParent extends PtyEndpoint {
|
public interface PtyParent extends PtyEndpoint {
|
||||||
void setWindowSize(int cols, int rows);
|
/**
|
||||||
|
* Resize the terminal window to the given width and height, in characters
|
||||||
|
*
|
||||||
|
* @param cols the width in characters
|
||||||
|
* @param rows the height in characters
|
||||||
|
*/
|
||||||
|
void setWindowSize(short cols, short rows);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,16 @@ import java.io.IOException;
|
||||||
import ghidra.pty.Pty;
|
import ghidra.pty.Pty;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
|
|
||||||
public class LinuxPtyFactory implements PtyFactory {
|
public enum LinuxPtyFactory implements PtyFactory {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pty openpty() throws IOException {
|
public Pty openpty(short cols, short rows) throws IOException {
|
||||||
return LinuxPty.openpty();
|
LinuxPty pty = LinuxPty.openpty();
|
||||||
|
if (cols != 0 && rows != 0) {
|
||||||
|
pty.getParent().setWindowSize(cols, rows);
|
||||||
|
}
|
||||||
|
return pty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,14 +24,10 @@ public class LinuxPtyParent extends LinuxPtyEndpoint implements PtyParent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWindowSize(int cols, int rows) {
|
public void setWindowSize(short cols, short rows) {
|
||||||
if (cols > 0xffff || rows > 0xffff) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Dimensions limited to unsigned shorts. Got cols=" + cols + ",rows=" + rows);
|
|
||||||
}
|
|
||||||
Winsize.ByReference ws = new Winsize.ByReference();
|
Winsize.ByReference ws = new Winsize.ByReference();
|
||||||
ws.ws_col = (short) cols;
|
ws.ws_col = cols;
|
||||||
ws.ws_row = (short) rows;
|
ws.ws_row = rows;
|
||||||
ws.write();
|
ws.write();
|
||||||
PosixC.INSTANCE.ioctl(fd, Winsize.TIOCSWINSZ, ws.getPointer());
|
PosixC.INSTANCE.ioctl(fd, Winsize.TIOCSWINSZ, ws.getPointer());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,16 @@ import ghidra.pty.Pty;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
import ghidra.pty.linux.LinuxPty;
|
import ghidra.pty.linux.LinuxPty;
|
||||||
|
|
||||||
public class MacosPtyFactory implements PtyFactory {
|
public enum MacosPtyFactory implements PtyFactory {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pty openpty() throws IOException {
|
public Pty openpty(short cols, short rows) throws IOException {
|
||||||
return LinuxPty.openpty();
|
LinuxPty pty = LinuxPty.openpty();
|
||||||
|
if (cols != 0 && rows != 0) {
|
||||||
|
pty.getParent().setWindowSize(cols, rows);
|
||||||
|
}
|
||||||
|
return pty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -227,12 +227,16 @@ public class GhidraSshPtyFactory implements PtyFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SshPty openpty() throws IOException {
|
public SshPty openpty(short cols, short rows) throws IOException {
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
session = connectAndAuthenticate();
|
session = connectAndAuthenticate();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return new SshPty((ChannelExec) session.openChannel("exec"));
|
SshPty pty = new SshPty((ChannelExec) session.openChannel("exec"));
|
||||||
|
if (cols != 0 && rows != 0) {
|
||||||
|
pty.getParent().setWindowSize(cols, rows);
|
||||||
|
}
|
||||||
|
return pty;
|
||||||
}
|
}
|
||||||
catch (JSchException e) {
|
catch (JSchException e) {
|
||||||
throw new IOException("SSH connection error", e);
|
throw new IOException("SSH connection error", e);
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class SshPtyParent extends SshPtyEndpoint implements PtyParent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWindowSize(int cols, int rows) {
|
public void setWindowSize(short cols, short rows) {
|
||||||
channel.setPtySize(cols, rows, 0, 0);
|
channel.setPtySize(Short.toUnsignedInt(cols), Short.toUnsignedInt(rows), 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,24 +20,18 @@ import java.io.IOException;
|
||||||
import com.sun.jna.platform.win32.Kernel32;
|
import com.sun.jna.platform.win32.Kernel32;
|
||||||
import com.sun.jna.platform.win32.WinDef.DWORD;
|
import com.sun.jna.platform.win32.WinDef.DWORD;
|
||||||
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
|
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
|
||||||
|
import com.sun.jna.platform.win32.COM.COMUtils;
|
||||||
|
|
||||||
import ghidra.pty.*;
|
import ghidra.pty.*;
|
||||||
import ghidra.pty.windows.jna.ConsoleApiNative;
|
import ghidra.pty.windows.jna.ConsoleApiNative;
|
||||||
import ghidra.pty.windows.jna.ConsoleApiNative.COORD;
|
import ghidra.pty.windows.jna.ConsoleApiNative.COORD;
|
||||||
|
|
||||||
import com.sun.jna.platform.win32.COM.COMUtils;
|
|
||||||
|
|
||||||
public class ConPty implements Pty {
|
public class ConPty implements Pty {
|
||||||
static final DWORD DW_ZERO = new DWORD(0);
|
static final DWORD DW_ZERO = new DWORD(0);
|
||||||
static final DWORD DW_ONE = new DWORD(1);
|
static final DWORD DW_ONE = new DWORD(1);
|
||||||
static final DWORD PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = new DWORD(0x20016);
|
static final DWORD PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = new DWORD(0x20016);
|
||||||
static final DWORD EXTENDED_STARTUPINFO_PRESENT =
|
static final DWORD EXTENDED_STARTUPINFO_PRESENT =
|
||||||
new DWORD(Kernel32.EXTENDED_STARTUPINFO_PRESENT);
|
new DWORD(Kernel32.EXTENDED_STARTUPINFO_PRESENT);
|
||||||
private static final COORD SIZE = new COORD();
|
|
||||||
static {
|
|
||||||
SIZE.X = Short.MAX_VALUE;
|
|
||||||
SIZE.Y = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Pipe pipeToChild;
|
private final Pipe pipeToChild;
|
||||||
private final Pipe pipeFromChild;
|
private final Pipe pipeFromChild;
|
||||||
|
@ -47,7 +41,7 @@ public class ConPty implements Pty {
|
||||||
private final ConPtyParent parent;
|
private final ConPtyParent parent;
|
||||||
private final ConPtyChild child;
|
private final ConPtyChild child;
|
||||||
|
|
||||||
public static ConPty openpty() {
|
public static ConPty openpty(short cols, short rows) {
|
||||||
// Create communication channels
|
// Create communication channels
|
||||||
|
|
||||||
Pipe pipeToChild = Pipe.createPipe();
|
Pipe pipeToChild = Pipe.createPipe();
|
||||||
|
@ -58,8 +52,11 @@ public class ConPty implements Pty {
|
||||||
|
|
||||||
HANDLEByReference lphPC = new HANDLEByReference();
|
HANDLEByReference lphPC = new HANDLEByReference();
|
||||||
|
|
||||||
|
COORD.ByValue size = new COORD.ByValue();
|
||||||
|
size.X = cols;
|
||||||
|
size.Y = rows;
|
||||||
COMUtils.checkRC(ConsoleApiNative.INSTANCE.CreatePseudoConsole(
|
COMUtils.checkRC(ConsoleApiNative.INSTANCE.CreatePseudoConsole(
|
||||||
SIZE,
|
size,
|
||||||
pipeToChild.getReadHandle().getNative(),
|
pipeToChild.getReadHandle().getNative(),
|
||||||
pipeFromChild.getWriteHandle().getNative(),
|
pipeFromChild.getWriteHandle().getNative(),
|
||||||
DW_ZERO,
|
DW_ZERO,
|
||||||
|
@ -76,7 +73,8 @@ public class ConPty implements Pty {
|
||||||
// TODO: See if this can all be combined with named pipes.
|
// TODO: See if this can all be combined with named pipes.
|
||||||
// Would be nice if that's sufficient to support new-ui
|
// Would be nice if that's sufficient to support new-ui
|
||||||
|
|
||||||
this.parent = new ConPtyParent(pipeToChild.getWriteHandle(), pipeFromChild.getReadHandle());
|
this.parent = new ConPtyParent(pipeToChild.getWriteHandle(), pipeFromChild.getReadHandle(),
|
||||||
|
pseudoConsoleHandle);
|
||||||
this.child = new ConPtyChild(pipeFromChild.getWriteHandle(), pipeToChild.getReadHandle(),
|
this.child = new ConPtyChild(pipeFromChild.getWriteHandle(), pipeToChild.getReadHandle(),
|
||||||
pseudoConsoleHandle);
|
pseudoConsoleHandle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,10 @@ import ghidra.pty.windows.jna.ConsoleApiNative;
|
||||||
import ghidra.pty.windows.jna.ConsoleApiNative.STARTUPINFOEX;
|
import ghidra.pty.windows.jna.ConsoleApiNative.STARTUPINFOEX;
|
||||||
|
|
||||||
public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
|
public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
|
||||||
private final Handle pseudoConsoleHandle;
|
|
||||||
|
|
||||||
public ConPtyChild(Handle writeHandle, Handle readHandle, Handle pseudoConsoleHandle) {
|
public ConPtyChild(Handle writeHandle, Handle readHandle,
|
||||||
super(writeHandle, readHandle);
|
PseudoConsoleHandle pseudoConsoleHandle) {
|
||||||
this.pseudoConsoleHandle = pseudoConsoleHandle;
|
super(writeHandle, readHandle, pseudoConsoleHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected STARTUPINFOEX prepareStartupInfo() {
|
protected STARTUPINFOEX prepareStartupInfo() {
|
||||||
|
|
|
@ -21,12 +21,15 @@ import java.io.OutputStream;
|
||||||
import ghidra.pty.PtyEndpoint;
|
import ghidra.pty.PtyEndpoint;
|
||||||
|
|
||||||
public class ConPtyEndpoint implements PtyEndpoint {
|
public class ConPtyEndpoint implements PtyEndpoint {
|
||||||
protected InputStream inputStream;
|
protected final InputStream inputStream;
|
||||||
protected OutputStream outputStream;
|
protected final OutputStream outputStream;
|
||||||
|
protected final PseudoConsoleHandle pseudoConsoleHandle;
|
||||||
|
|
||||||
public ConPtyEndpoint(Handle writeHandle, Handle readHandle) {
|
public ConPtyEndpoint(Handle writeHandle, Handle readHandle,
|
||||||
|
PseudoConsoleHandle pseudoConsoleHandle) {
|
||||||
this.inputStream = new HandleInputStream(readHandle);
|
this.inputStream = new HandleInputStream(readHandle);
|
||||||
this.outputStream = new HandleOutputStream(writeHandle);
|
this.outputStream = new HandleOutputStream(writeHandle);
|
||||||
|
this.pseudoConsoleHandle = pseudoConsoleHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,10 +20,15 @@ import java.io.IOException;
|
||||||
import ghidra.pty.Pty;
|
import ghidra.pty.Pty;
|
||||||
import ghidra.pty.PtyFactory;
|
import ghidra.pty.PtyFactory;
|
||||||
|
|
||||||
public class ConPtyFactory implements PtyFactory {
|
public enum ConPtyFactory implements PtyFactory {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pty openpty() throws IOException {
|
public Pty openpty(short cols, short rows) throws IOException {
|
||||||
return ConPty.openpty();
|
if (cols == 0 || rows == 0) {
|
||||||
|
return ConPty.openpty((short) 80, (short) 25);
|
||||||
|
}
|
||||||
|
return ConPty.openpty(cols, rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,15 +16,15 @@
|
||||||
package ghidra.pty.windows;
|
package ghidra.pty.windows;
|
||||||
|
|
||||||
import ghidra.pty.PtyParent;
|
import ghidra.pty.PtyParent;
|
||||||
import ghidra.util.Msg;
|
|
||||||
|
|
||||||
public class ConPtyParent extends ConPtyEndpoint implements PtyParent {
|
public class ConPtyParent extends ConPtyEndpoint implements PtyParent {
|
||||||
public ConPtyParent(Handle writeHandle, Handle readHandle) {
|
public ConPtyParent(Handle writeHandle, Handle readHandle,
|
||||||
super(writeHandle, readHandle);
|
PseudoConsoleHandle pseudoConsoleHandle) {
|
||||||
|
super(writeHandle, readHandle, pseudoConsoleHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWindowSize(int rows, int cols) {
|
public void setWindowSize(short cols, short rows) {
|
||||||
Msg.error(this, "Pty window size not implemented on Windows");
|
pseudoConsoleHandle.resize(rows, cols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class Handle implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() {
|
||||||
cleanable.clean();
|
cleanable.clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,8 @@ public class HandleInputStream extends InputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void close() throws IOException {
|
public void close() throws IOException {
|
||||||
closed = true;
|
closed = true;
|
||||||
|
handle.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
package ghidra.pty.windows;
|
package ghidra.pty.windows;
|
||||||
|
|
||||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||||
|
import com.sun.jna.platform.win32.COM.COMUtils;
|
||||||
|
|
||||||
import ghidra.pty.windows.jna.ConsoleApiNative;
|
import ghidra.pty.windows.jna.ConsoleApiNative;
|
||||||
|
import ghidra.pty.windows.jna.ConsoleApiNative.COORD;
|
||||||
|
|
||||||
public class PseudoConsoleHandle extends Handle {
|
public class PseudoConsoleHandle extends Handle {
|
||||||
|
|
||||||
|
@ -40,4 +42,11 @@ public class PseudoConsoleHandle extends Handle {
|
||||||
protected State newState(HANDLE handle) {
|
protected State newState(HANDLE handle) {
|
||||||
return new PseudoConsoleState(handle);
|
return new PseudoConsoleState(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resize(short rows, short cols) {
|
||||||
|
COORD.ByValue size = new COORD.ByValue();
|
||||||
|
size.X = cols;
|
||||||
|
size.Y = rows;
|
||||||
|
COMUtils.checkRC(ConsoleApiNative.INSTANCE.ResizePseudoConsole(getNative(), size));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.pty.windows.jna;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.*;
|
||||||
|
import com.sun.jna.Structure.FieldOrder;
|
||||||
import com.sun.jna.platform.win32.WinBase;
|
import com.sun.jna.platform.win32.WinBase;
|
||||||
import com.sun.jna.platform.win32.WinDef.*;
|
import com.sun.jna.platform.win32.WinDef.*;
|
||||||
import com.sun.jna.platform.win32.WinNT.*;
|
import com.sun.jna.platform.win32.WinNT.*;
|
||||||
|
@ -31,8 +32,9 @@ public interface ConsoleApiNative extends StdCallLibrary {
|
||||||
SECURITY_ATTRIBUTES.ByReference lpPipeAttributes, DWORD nSize);
|
SECURITY_ATTRIBUTES.ByReference lpPipeAttributes, DWORD nSize);
|
||||||
|
|
||||||
HRESULT CreatePseudoConsole(COORD.ByValue size, HANDLE hInput, HANDLE hOutput,
|
HRESULT CreatePseudoConsole(COORD.ByValue size, HANDLE hInput, HANDLE hOutput,
|
||||||
DWORD dwFlags,
|
DWORD dwFlags, HANDLEByReference phPC);
|
||||||
HANDLEByReference phPC);
|
|
||||||
|
HRESULT ResizePseudoConsole(HANDLE hPC, COORD.ByValue size);
|
||||||
|
|
||||||
void ClosePseudoConsole(HANDLE hPC);
|
void ClosePseudoConsole(HANDLE hPC);
|
||||||
|
|
||||||
|
@ -85,20 +87,16 @@ public interface ConsoleApiNative extends StdCallLibrary {
|
||||||
HANDLEByReference phToken);
|
HANDLEByReference phToken);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static class COORD extends Structure implements Structure.ByValue {
|
@FieldOrder({ "X", "Y" })
|
||||||
public static class ByReference extends COORD
|
public static class COORD extends Structure {
|
||||||
implements Structure.ByReference {
|
public static class ByValue extends COORD implements Structure.ByValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final List<String> FIELDS = createFieldsOrder("X", "Y");
|
public static class ByReference extends COORD implements Structure.ByReference {
|
||||||
|
}
|
||||||
|
|
||||||
public short X;
|
public short X;
|
||||||
public short Y;
|
public short Y;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<String> getFieldOrder() {
|
|
||||||
return FIELDS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SECURITY_ATTRIBUTES extends Structure {
|
public static class SECURITY_ATTRIBUTES extends Structure {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ghidra.app.script.AskDialog;
|
import ghidra.app.script.AskDialog;
|
||||||
|
import ghidra.pty.Pty;
|
||||||
import ghidra.pty.PtyChild.Echo;
|
import ghidra.pty.PtyChild.Echo;
|
||||||
import ghidra.pty.PtySession;
|
import ghidra.pty.PtySession;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
@ -77,7 +78,7 @@ public class SshPtyTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSessionBash() throws IOException, InterruptedException {
|
public void testSessionBash() throws IOException, InterruptedException {
|
||||||
try (SshPty pty = factory.openpty()) {
|
try (Pty pty = factory.openpty()) {
|
||||||
PtySession bash = pty.getChild().session(new String[] { "bash" }, null);
|
PtySession bash = pty.getChild().session(new String[] { "bash" }, null);
|
||||||
OutputStream out = pty.getParent().getOutputStream();
|
OutputStream out = pty.getParent().getOutputStream();
|
||||||
out.write("exit\n".getBytes("UTF-8"));
|
out.write("exit\n".getBytes("UTF-8"));
|
||||||
|
@ -89,7 +90,7 @@ public class SshPtyTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDisableEcho() throws IOException, InterruptedException {
|
public void testDisableEcho() throws IOException, InterruptedException {
|
||||||
try (SshPty pty = factory.openpty()) {
|
try (Pty pty = factory.openpty()) {
|
||||||
PtySession bash =
|
PtySession bash =
|
||||||
pty.getChild().session(new String[] { "bash" }, null, Echo.OFF);
|
pty.getChild().session(new String[] { "bash" }, null, Echo.OFF);
|
||||||
OutputStream out = pty.getParent().getOutputStream();
|
OutputStream out = pty.getParent().getOutputStream();
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class ConPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSessionCmd() throws IOException, InterruptedException {
|
public void testSessionCmd() throws IOException, InterruptedException {
|
||||||
try (Pty pty = ConPty.openpty()) {
|
try (Pty pty = ConPtyFactory.INSTANCE.openpty()) {
|
||||||
PtySession cmd = pty.getChild().session(new String[] { DummyProc.which("cmd") }, null);
|
PtySession cmd = pty.getChild().session(new String[] { DummyProc.which("cmd") }, null);
|
||||||
pty.getParent().getOutputStream().write("exit\r\n".getBytes());
|
pty.getParent().getOutputStream().write("exit\r\n".getBytes());
|
||||||
assertEquals(0, cmd.waitExited());
|
assertEquals(0, cmd.waitExited());
|
||||||
|
@ -49,7 +49,7 @@ public class ConPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSessionNonExistent() throws IOException, InterruptedException {
|
public void testSessionNonExistent() throws IOException, InterruptedException {
|
||||||
try (Pty pty = ConPty.openpty()) {
|
try (Pty pty = ConPtyFactory.INSTANCE.openpty()) {
|
||||||
pty.getChild().session(new String[] { "thisHadBetterNoExist" }, null);
|
pty.getChild().session(new String[] { "thisHadBetterNoExist" }, null);
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ public class ConPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSessionCmdEchoTest() throws IOException, InterruptedException {
|
public void testSessionCmdEchoTest() throws IOException, InterruptedException {
|
||||||
try (Pty pty = ConPty.openpty()) {
|
try (Pty pty = ConPtyFactory.INSTANCE.openpty()) {
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||||
BufferedReader reader = loggingReader(parent.getInputStream());
|
BufferedReader reader = loggingReader(parent.getInputStream());
|
||||||
|
@ -84,7 +84,7 @@ public class ConPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSessionGdbLineLength() throws IOException, InterruptedException {
|
public void testSessionGdbLineLength() throws IOException, InterruptedException {
|
||||||
try (Pty pty = ConPty.openpty()) {
|
try (Pty pty = ConPtyFactory.INSTANCE.openpty()) {
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||||
BufferedReader reader = loggingReader(parent.getInputStream());
|
BufferedReader reader = loggingReader(parent.getInputStream());
|
||||||
|
@ -137,7 +137,7 @@ public class ConPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGdbInterruptConPty() throws Exception {
|
public void testGdbInterruptConPty() throws Exception {
|
||||||
try (Pty pty = ConPty.openpty()) {
|
try (Pty pty = ConPtyFactory.INSTANCE.openpty()) {
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||||
//BufferedReader reader = loggingReader(parent.getInputStream());
|
//BufferedReader reader = loggingReader(parent.getInputStream());
|
||||||
|
@ -171,7 +171,7 @@ public class ConPtyTest extends AbstractPtyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGdbMiConPty() throws Exception {
|
public void testGdbMiConPty() throws Exception {
|
||||||
try (Pty pty = ConPty.openpty()) {
|
try (Pty pty = ConPtyFactory.INSTANCE.openpty()) {
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||||
//BufferedReader reader = loggingReader(parent.getInputStream());
|
//BufferedReader reader = loggingReader(parent.getInputStream());
|
||||||
|
|
|
@ -23,7 +23,9 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import docking.widgets.OkDialog;
|
import docking.widgets.OkDialog;
|
||||||
|
@ -38,7 +40,7 @@ import ghidra.util.SystemUtilities;
|
||||||
public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
protected static byte[] ascii(String str) {
|
protected static byte[] ascii(String str) {
|
||||||
try {
|
try {
|
||||||
return str.getBytes("US-ASCII");
|
return str.getBytes("UTF-8");
|
||||||
}
|
}
|
||||||
catch (UnsupportedEncodingException e) {
|
catch (UnsupportedEncodingException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
|
@ -68,15 +70,17 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
PtySession session = pty.getChild().session(new String[] { "/usr/bin/bash" }, env);
|
||||||
|
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("US-ASCII"),
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream())) {
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
term.addTerminalListener(new TerminalListener() {
|
term.addTerminalListener(new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(int cols, int rows) {
|
public void resized(short cols, short rows) {
|
||||||
|
System.err.println("resized: " + cols + "x" + rows);
|
||||||
parent.setWindowSize(cols, rows);
|
parent.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
session.waitExited();
|
session.waitExited();
|
||||||
|
pty.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,18 +98,45 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
try (Pty pty = factory.openpty()) {
|
try (Pty pty = factory.openpty()) {
|
||||||
Map<String, String> env = new HashMap<>(System.getenv());
|
Map<String, String> env = new HashMap<>(System.getenv());
|
||||||
PtySession session =
|
PtySession session =
|
||||||
pty.getChild().session(new String[] { "C:\\Windows\\cmd.exe" }, env);
|
pty.getChild().session(new String[] { "C:\\Windows\\system32\\cmd.exe" }, env);
|
||||||
|
|
||||||
PtyParent parent = pty.getParent();
|
PtyParent parent = pty.getParent();
|
||||||
try (Terminal term = terminalService.createWithStreams(Charset.forName("US-ASCII"),
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
parent.getInputStream(), parent.getOutputStream())) {
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
term.addTerminalListener(new TerminalListener() {
|
term.addTerminalListener(new TerminalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resized(int cols, int rows) {
|
public void resized(short cols, short rows) {
|
||||||
|
System.err.println("resized: " + cols + "x" + rows);
|
||||||
parent.setWindowSize(cols, rows);
|
parent.setWindowSize(cols, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
session.waitExited();
|
session.waitExited();
|
||||||
|
pty.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void testCmd80x25() throws Exception {
|
||||||
|
assumeFalse(SystemUtilities.isInTestingBatchMode());
|
||||||
|
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS);
|
||||||
|
|
||||||
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
clipboardService = addPlugin(tool, ClipboardPlugin.class);
|
||||||
|
|
||||||
|
PtyFactory factory = PtyFactory.local();
|
||||||
|
try (Pty pty = factory.openpty(80, 25)) {
|
||||||
|
Map<String, String> env = new HashMap<>(System.getenv());
|
||||||
|
PtySession session =
|
||||||
|
pty.getChild().session(new String[] { "C:\\Windows\\system32\\cmd.exe" }, env);
|
||||||
|
|
||||||
|
PtyParent parent = pty.getParent();
|
||||||
|
try (Terminal term = terminalService.createWithStreams(Charset.forName("UTF-8"),
|
||||||
|
parent.getInputStream(), parent.getOutputStream())) {
|
||||||
|
term.setFixedSize(80, 25);
|
||||||
|
session.waitExited();
|
||||||
|
pty.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,9 +154,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("term");
|
term.provider.findDialog.txtFind.setText("term");
|
||||||
|
@ -155,9 +186,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("term");
|
term.provider.findDialog.txtFind.setText("term");
|
||||||
|
@ -184,9 +215,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("term");
|
term.provider.findDialog.txtFind.setText("term");
|
||||||
|
@ -216,9 +247,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("term");
|
term.provider.findDialog.txtFind.setText("term");
|
||||||
|
@ -245,9 +276,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("o?term");
|
term.provider.findDialog.txtFind.setText("o?term");
|
||||||
|
@ -283,9 +314,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("term");
|
term.provider.findDialog.txtFind.setText("term");
|
||||||
|
@ -315,9 +346,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("term");
|
term.provider.findDialog.txtFind.setText("term");
|
||||||
|
@ -347,9 +378,9 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
terminalService = addPlugin(tool, TerminalPlugin.class);
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
.createNullTerminal(Charset.forName("US-ASCII"), buf -> {
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
})) {
|
})) {
|
||||||
term.setFixedSize(25, 80);
|
term.setFixedSize(80, 25);
|
||||||
term.injectDisplayOutput(TEST_CONTENTS);
|
term.injectDisplayOutput(TEST_CONTENTS);
|
||||||
|
|
||||||
term.provider.findDialog.txtFind.setText("o?term");
|
term.provider.findDialog.txtFind.setText("o?term");
|
||||||
|
@ -378,4 +409,190 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String csi(char f, int... params) {
|
||||||
|
return "\033[" +
|
||||||
|
IntStream.of(params).mapToObj(Integer::toString).collect(Collectors.joining(";")) + f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String title(String title) {
|
||||||
|
return "\033]0;" + title + "\007";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final static String HIDE_CURSOR = "\033[?25l";
|
||||||
|
protected final static String SHOW_CURSOR = "\033[?25h";
|
||||||
|
|
||||||
|
protected void send(Terminal term, String... parts) throws Exception {
|
||||||
|
String joined = Stream.of(parts).collect(Collectors.joining());
|
||||||
|
term.injectDisplayOutput(joined.getBytes("UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void testSimulateLinuxPtyResetAndEol() throws Exception {
|
||||||
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
|
})) {
|
||||||
|
term.setFixedSize(40, 25);
|
||||||
|
|
||||||
|
send(term,
|
||||||
|
csi('J', 3), csi('H'), csi('J', 2),
|
||||||
|
title(name.getMethodName()),
|
||||||
|
/**
|
||||||
|
* Linux/bash goes one character past, sends CR, repeats the character, and
|
||||||
|
* continues. No LF. The line feed occurs by virtue of the local line wrap.
|
||||||
|
*/
|
||||||
|
"12345678901234567890123456789012345678901",
|
||||||
|
"\r",
|
||||||
|
"1234567890");
|
||||||
|
|
||||||
|
assertEquals("1234567890123456789012345678901234567890", term.getLineText(0));
|
||||||
|
assertEquals("1234567890", term.getLineText(1));
|
||||||
|
assertEquals(10, term.getCursorColumn());
|
||||||
|
assertEquals(1, term.getCursorRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void testSimulateLinuxPtyTypePastEol() throws Exception {
|
||||||
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
|
})) {
|
||||||
|
term.setFixedSize(40, 25);
|
||||||
|
|
||||||
|
send(term,
|
||||||
|
title(name.getMethodName()),
|
||||||
|
/**
|
||||||
|
* Echoing characters back works similarly to sending characters. When the last
|
||||||
|
* column is filled (the application knows the terminal width) Linux/bash sends an
|
||||||
|
* extra space to induce a line wrap, then sends CR.
|
||||||
|
*/
|
||||||
|
"123456789012345678901234567890123456789", // One before the last column
|
||||||
|
"0 \r");
|
||||||
|
|
||||||
|
assertEquals("1234567890123456789012345678901234567890", term.getLineText(0));
|
||||||
|
assertEquals(" ", term.getLineText(1));
|
||||||
|
assertEquals(0, term.getCursorColumn());
|
||||||
|
assertEquals(1, term.getCursorRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void testSimulateLinuxPtyEchoPastEol() throws Exception {
|
||||||
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
|
})) {
|
||||||
|
term.setFixedSize(40, 25);
|
||||||
|
|
||||||
|
send(term,
|
||||||
|
title(name.getMethodName()),
|
||||||
|
/**
|
||||||
|
* The echo command itself pays no heed to the terminal width. Wrapping is purely
|
||||||
|
* terminal side.
|
||||||
|
*/
|
||||||
|
"1234567890123456789012345678901234567890asdfasdf\r\n");
|
||||||
|
|
||||||
|
assertEquals("1234567890123456789012345678901234567890", term.getLineText(0));
|
||||||
|
assertEquals("asdfasdf", term.getLineText(1));
|
||||||
|
assertEquals("", term.getLineText(2));
|
||||||
|
assertEquals(0, term.getCursorColumn());
|
||||||
|
assertEquals(2, term.getCursorRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void testSimulateLinuxPtyTypePastEolLastLine() throws Exception {
|
||||||
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
|
})) {
|
||||||
|
term.setFixedSize(40, 25);
|
||||||
|
|
||||||
|
send(term,
|
||||||
|
title(name.getMethodName()),
|
||||||
|
"top line\r\n",
|
||||||
|
StringUtils.repeat("\r\n", 23),
|
||||||
|
"123456789012345678901234567890123456789", // One before the last column
|
||||||
|
"0 \r");
|
||||||
|
|
||||||
|
assertEquals(1, term.getScrollBackRows());
|
||||||
|
assertEquals("top line", term.getLineText(-1));
|
||||||
|
assertEquals("1234567890123456789012345678901234567890", term.getLineText(23));
|
||||||
|
assertEquals(" ", term.getLineText(24));
|
||||||
|
assertEquals(0, term.getCursorColumn());
|
||||||
|
assertEquals(24, term.getCursorRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void testSimulateWindowsPtyStartCmd() throws Exception {
|
||||||
|
terminalService = addPlugin(tool, TerminalPlugin.class);
|
||||||
|
|
||||||
|
try (DefaultTerminal term = (DefaultTerminal) terminalService
|
||||||
|
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
|
||||||
|
})) {
|
||||||
|
term.setFixedSize(80, 25);
|
||||||
|
|
||||||
|
send(term,
|
||||||
|
csi('J', 2), HIDE_CURSOR, csi('m'), csi('H'),
|
||||||
|
StringUtils.repeat("\r\n", 32), // for a 25-line terminal? No matter.
|
||||||
|
csi('H'),
|
||||||
|
title("C:\\Windows\\system32\\cmd.exe\0"),
|
||||||
|
SHOW_CURSOR, HIDE_CURSOR,
|
||||||
|
// Line 1: Length is 43
|
||||||
|
"Microsoft Windows [Version XXXXXXXXXXXXXXX]",
|
||||||
|
csi('X', 37), csi('C', 37), "\r\n", // 37 + 43 = 80
|
||||||
|
// Line 2: Length is 52
|
||||||
|
"(c) 20XX Microsoft Corporation. All rights reserved.",
|
||||||
|
csi('X', 28), csi('C', 28), "\r\n", // 28 + 52 = 80
|
||||||
|
// Line 3: Blank
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n", // 80 + 0 = 80
|
||||||
|
// Line 4: No X or C sequences. Probably because shorter to put literal " "
|
||||||
|
"C:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX> ");
|
||||||
|
send(term, " ");
|
||||||
|
send(term, "\r\n");
|
||||||
|
send(term,
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
csi('X', 80), csi('C', 80), "\r\n",
|
||||||
|
|
||||||
|
csi('X', 80), csi('C', 80),
|
||||||
|
csi('H', 4, 79),
|
||||||
|
SHOW_CURSOR);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"C:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX> ",
|
||||||
|
term.getLineText(3));
|
||||||
|
assertEquals(78, term.getCursorColumn());
|
||||||
|
assertEquals(3, term.getCursorRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue