windows-nt/Source/XPSP1/NT/base/subsys/posix/programs/psxses/terminp.c
2020-09-26 16:20:57 +08:00

718 lines
16 KiB
C

#define WIN32_ONLY
#include "psxses.h"
#include "ansiio.h"
#include <posix/sys/types.h>
#include <posix/termios.h>
#include <io.h>
#include <stdio.h>
#define _POSIX_
#include <posix/unistd.h> // for _POSIX_VDISABLE
#include <posix/sys/errno.h> // for EAGAIN
/* Keyboard Input Buffering Support */
#define KBD_BUFFER_SIZE 512 // XXX.mjb: should use {MAX_INPUT}
#define INPUT_EVENT_CLUSTER 1
#define CR 0x0d // carriage return
#define NL 0x0a // newline (aka line feed)
//
// We keep an array of screen positions of tab characters. These
// are used when backspacing over tabs to know where to position
// the cursor. NumTabs is the number of markers in use.
//
#define NUM_TAB_MARKERS (KBD_BUFFER_SIZE)
/*STATIC*/ int TabMarkers[NUM_TAB_MARKERS];
/*STATIC*/ int NumTabs;
extern DWORD InputModeFlags; // console input mode
extern DWORD OutputModeFlags; // Console Output Mode
extern unsigned char AnsiNewMode;
extern struct termios SavedTermios; // saved tty settings
extern HANDLE
hIoEvent,
hStopEvent,
hCanonEvent;
extern CRITICAL_SECTION StopMutex;
/*STATIC*/ int InputEOF; // input at EOF.
/*STATIC*/ INT DelKbdBuffer(CHAR *CharBuf, DWORD CharCnt);
//
// SignalInterrupt is also protected by KbdBufMutex; when set,
// indicates that EINTR should be returned from read.
//
/*STATIC*/ int SignalInterrupt = FALSE;
extern CRITICAL_SECTION KbdBufMutex;
//
// XXX.mjb: Should implement a dynamic structure so there is no
// limitation on input (if buffer is full, cannot terminate process
// with ^C or other interrupt character
//
CHAR KbdBuffer[KBD_BUFFER_SIZE];
DWORD KbdBufferHead = 0, // position of queue head
KbdBufferTail = 0, // position of queue tail
KbdBufferCount = 0, // chars in queue
LineCount = 0; // lines in queue
//
// TermInput -- get input from kbd buffer and return to user.
//
// Return number of bytes retrieved; -1 indicates EINTR
// should occur.
//
ssize_t
TermInput(
IN HANDLE cs,
OUT LPSTR DestStr,
IN DWORD cnt,
IN int flags,
OUT int *pError
)
{
DWORD BytesRead = 0;
BOOL bSuccess;
BOOL StopInput = FALSE;
CHAR *PDestStr = (CHAR *)DestStr;
CHAR AsciiChar;
//
// Deal with non-blocking input. If canonical mode is set,
// we return EAGAIN unless an entire line is available. If
// canonical mode is not set, we return EAGAIN unless at least
// one character is available.
//
if (flags & PSXSES_NONBLOCK) {
if (SavedTermios.c_lflag & ICANON) {
if (0 == LineCount) {
*pError = EAGAIN;
return -1;
}
} else {
if (0 == KbdBufferCount) {
*pError = EAGAIN;
return -1;
}
}
}
//
// Handle canonical input on consumer side: don't return until
// an entire line is ready to return. We don't wait around to
// accumulate a line if EOF has occured on input.
//
if ((SavedTermios.c_lflag & ICANON) && !InputEOF) {
WaitForSingleObject(hCanonEvent, INFINITE);
}
if (SignalInterrupt) {
EnterCriticalSection(&KbdBufMutex);
if (SignalInterrupt) {
SignalInterrupt = FALSE;
LeaveCriticalSection(&KbdBufMutex);
ResetEvent(hCanonEvent);
*pError = EINTR;
return -1;
}
LeaveCriticalSection(&KbdBufMutex);
}
if (0 == KbdBufferCount && InputEOF) {
ResetEvent(hCanonEvent);
ResetEvent(hIoEvent);
InputEOF = FALSE;
EnterCriticalSection(&KbdBufMutex);
LineCount = 0;
LeaveCriticalSection(&KbdBufMutex);
*pError = 0;
return 0;
}
while (BytesRead < cnt && !StopInput) {
if (0 == KbdBufferCount && InputEOF) {
ResetEvent(hCanonEvent);
ResetEvent(hIoEvent);
EnterCriticalSection(&KbdBufMutex);
LeaveCriticalSection(&KbdBufMutex);
*pError = 0;
return BytesRead;
}
//
// Wait for input queue to be non-empty
//
WaitForSingleObject(hIoEvent, INFINITE);
EnterCriticalSection(&KbdBufMutex);
DelKbdBuffer(&AsciiChar, 1);
LeaveCriticalSection(&KbdBufMutex);
if ((SavedTermios.c_lflag & ICANON) && '\n' == AsciiChar) {
StopInput = TRUE;
EnterCriticalSection(&KbdBufMutex);
//
// Only reset canonical processing event when there are NO
// lines in input queue.
//
if (--LineCount == 0) {
bSuccess = ResetEvent(hCanonEvent);
ASSERT(bSuccess);
}
LeaveCriticalSection(&KbdBufMutex);
}
*PDestStr++ = AsciiChar;
BytesRead++;
//
// XXX.mjb: In noncanonical mode, return even one character. This is a
// fudge on the POSIX standard. Should consider MIN and TIME later.
//
// XXX.mjb: What if a character is removed from the buffer (by
// backspace) between the time we check to see if there is one
// and the time we call DelKbdBuffer? Could we end up waiting
// on hIoEvent in non-canonical mode after characters have been
// read?
//
if (!(SavedTermios.c_lflag & ICANON) && 0 == KbdBufferCount) {
*pError = 0;
return BytesRead;
}
}
*pError = 0;
return BytesRead;
}
//
// BkspcKbdBuffer - do a backspace, removing a character from the
// input buffer. Arrange for the character to be removed from
// the display, if bEcho is set.
//
// bEcho - echo the character?
//
// Returns:
//
// TRUE if a character was removed from the buffer.
// FALSE otherwise (maybe no chars left in line).
//
BOOL
BkspcKbdBuffer(
BOOLEAN bEcho
)
{
char *kill_char;
int prev_pos, i;
if (bEcho) {
kill_char = "\010 \010";
} else {
kill_char = "\010";
}
if (0 == KbdBufferCount) {
/* Empty buffer */
return FALSE;
}
if ('\n' == KbdBuffer[KbdBufferTail - 1]) {
// bkspc should not erase beyond start of line.
return FALSE;
}
KbdBufferCount--;
if (KbdBufferTail == 0)
KbdBufferTail = KBD_BUFFER_SIZE - 1;
else
KbdBufferTail--;
if ('\t' == KbdBuffer[KbdBufferTail]) {
int dif;
--NumTabs;
dif = TrackedCoord.X - TabMarkers[NumTabs];
if (1 == TrackedCoord.X) {
char buf[10];
ULONG save;
// bkspc over tab causes reverse wrap.
save = OutputModeFlags;
OutputModeFlags &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
sprintf(buf, "\x1b[%d;%dH", TrackedCoord.Y - 1,
TabMarkers[NumTabs]);
TermOutput(hConsoleOutput, buf, strlen(buf));
OutputModeFlags = save;
return TRUE;
}
for (i = 0; i < dif; ++i) {
TermOutput(hConsoleOutput, "\010", 1);
}
return TRUE;
}
if (1 == TrackedCoord.X) {
// remove a character that will cause a reverse wrap.
char buf[10];
ULONG save;
// Disable wrap to make cursor positioning right.
save = OutputModeFlags;
OutputModeFlags &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
sprintf(buf, "\x1b[%d;%dH", TrackedCoord.Y - 1,
ScreenColNum + 1);
if (bEcho) {
strcat(buf, " ");
}
TermOutput(hConsoleOutput, buf, strlen(buf));
OutputModeFlags = save;
return TRUE;
}
//
// Remove an ordinary character.
//
TermOutput(hConsoleOutput, kill_char, 3);
return TRUE;
}
//
// KillKbdBuffer - execute a line-kill, removing the last line
// from the input buffer. Arrange for the killed characters
// to be removed from the display.
//
// Returns:
//
// TRUE if some characters were killed.
// FALSE otherwise.
//
BOOL
KillKbdBuffer(
VOID
)
{
BOOLEAN bEchoK;
if (0 == KbdBufferCount) {
/* Empty buffer */
return FALSE;
}
if ('\n' == KbdBuffer[KbdBufferTail - 1]) {
// kill should not erase beyond start of line.
return FALSE;
}
bEchoK = ((SavedTermios.c_lflag & ECHOK) == ECHOK);
while (BkspcKbdBuffer(bEchoK))
;
return TRUE;
}
//
// AddKbdBuffer -- add a character to the input queue.
//
// Characters have been typed, and they should be added to the
// input queue.
//
//
BOOL
AddKbdBuffer(
PCHAR Buf,
int Cnt
)
{
CHAR *pc, *pcLast;
char *kill_char = "\010 \010";
int prev_pos, i;
for (pc = Buf, pcLast = Buf + Cnt; pc < pcLast; pc++) {
if (KbdBufferCount == KBD_BUFFER_SIZE) {
KdPrint(("PSXSES: keyboard buffer overflowed\n"));
return FALSE;
}
KbdBuffer[KbdBufferTail] = *pc;
KbdBufferCount++;
if ('\t' == *pc) {
//
// The user has input a tab. We save the screen position
// of the character
//
TabMarkers[NumTabs++] = TrackedCoord.X;
}
if (++KbdBufferTail == KBD_BUFFER_SIZE)
KbdBufferTail = 0;
if (KbdBufferCount == 1 && !SetEvent(hIoEvent)) {
KdPrint(("PSXSES: failed to set input synch event: 0x%x\n",
GetLastError()));
}
}
return TRUE;
}
INT
DelKbdBuffer(CHAR *CharBuf, DWORD CharCnt)
{
CHAR *AsciiChar, *LastChar;
for ( AsciiChar = CharBuf, LastChar = (CharBuf + CharCnt);
AsciiChar < LastChar; AsciiChar++ ) {
if ( !KbdBufferCount ) { /* Empty buffer */
return (FALSE);
}
*AsciiChar = KbdBuffer[KbdBufferHead];
KbdBufferCount--;
if ( ++KbdBufferHead == KBD_BUFFER_SIZE )
KbdBufferHead = 0;
/* Buffer becomes empty */
if ( !KbdBufferCount && !ResetEvent(hIoEvent) ) {
KdPrint(("PSXSES: failed to reset input synch event\n"));
}
} /* for */
return (TRUE);
}
//
// ServeKbdInput --
//
// Run as a separate thread for asynchronous console input.
// Get keydown events from the console window and fill the keyboard
// buffer structure with ASCII values
//
VOID
ServeKbdInput(LPVOID Unused)
{
BOOL bSuccess;
BOOL BackSpaceFound = FALSE;
BOOL LineKillFound = FALSE;
BOOL bSignalInterrupt = FALSE;
INPUT_RECORD inputBuffer[INPUT_EVENT_CLUSTER];
PKEY_EVENT_RECORD pKeyEvent;
DWORD dwEventsRead; /* number of events actually read */
UCHAR LocalBuf[10], AsciiChar;
int LocalCnt;
LPDWORD count;
DWORD i;
for (;;) {
bSuccess = ReadConsoleInput(hConsoleInput, inputBuffer,
INPUT_EVENT_CLUSTER, &dwEventsRead);
if (!bSuccess) {
KdPrint(("posix: ReadConsoleInput: 0x%x\n", GetLastError()));
ExitThread(1);
}
for (i = 0; i < dwEventsRead; i++) {
LocalCnt = 0;
BackSpaceFound = FALSE;
LineKillFound = FALSE;
if (inputBuffer[i].EventType != KEY_EVENT) {
continue;
}
pKeyEvent = &inputBuffer[i].Event.KeyEvent;
if (!pKeyEvent->bKeyDown) {
//
// Only interested in key-down events.
//
continue;
}
//
// Map a Key event to one or more ASCII characters,
// in local buffer. AsciiChar should be maintained
// as last char typed.
//
AsciiChar = pKeyEvent->uChar.AsciiChar;
// pKeyEvent->wRepeatCount
switch (pKeyEvent->wVirtualKeyCode) {
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
case VK_CAPITAL:
case VK_NUMLOCK:
case VK_SCROLL:
//
// We're not interested in the fact that one
// of these keys has been pressed; we'll deal
// with them later as a modifier on a normal
// key press.
//
continue;
case VK_DELETE:
AsciiChar = CTRL('?');
break;
case VK_F1:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\073';
break;
case VK_F2:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\074';
break;
case VK_F3:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\075';
break;
case VK_F4:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\076';
break;
case VK_F5:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\077';
break;
case VK_F6:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\100';
break;
case VK_F7:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\101';
break;
case VK_F8:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\102';
break;
case VK_F9:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\103';
break;
case VK_F10:
LocalBuf[LocalCnt++] = (UCHAR)'\200';
AsciiChar = '\104';
break;
case VK_LEFT:
LocalBuf[LocalCnt++] = ANSI_ESC;
LocalBuf[LocalCnt++] = '[';
AsciiChar = 'D';
break;
case VK_UP:
LocalBuf[LocalCnt++] = ANSI_ESC;
LocalBuf[LocalCnt++] = '[';
AsciiChar = 'A';
break;
case VK_RIGHT:
LocalBuf[LocalCnt++] = ANSI_ESC;
LocalBuf[LocalCnt++] = '[';
AsciiChar = 'C';
break;
case VK_DOWN:
LocalBuf[LocalCnt++] = ANSI_ESC;
LocalBuf[LocalCnt++] = '[';
AsciiChar = 'B';
break;
default:
break;
}
if (pKeyEvent->dwControlKeyState &
(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {
//
// Convert Control + key to ^key.
//
AsciiChar = CTRL(AsciiChar);
}
if (SavedTermios.c_iflag & ISTRIP) {
//
// XXX.mjb: not sure if we should strip here or after
// special character processing.
//
AsciiChar &= 0x7F;
}
if ((SavedTermios.c_iflag & IXOFF) ||
(SavedTermios.c_iflag & IXON)) {
if (AsciiChar == SavedTermios.c_cc[VSTOP]) {
EnterCriticalSection(&StopMutex);
bStop = TRUE;
ResetEvent(hStopEvent);
LeaveCriticalSection(&StopMutex);
goto discard;
}
if (AsciiChar == SavedTermios.c_cc[VSTART]) {
EnterCriticalSection(&StopMutex);
bStop = FALSE;
SetEvent(hStopEvent);
LeaveCriticalSection(&StopMutex);
goto discard;
}
}
if (SavedTermios.c_lflag & ISIG) {
if (AsciiChar == SavedTermios.c_cc[VINTR] &&
SavedTermios.c_cc[VINTR] != _POSIX_VDISABLE) {
//
// Send an interrupt to all processes in the
// foreground process group
//
bSignalInterrupt = TRUE;
SignalSession(PSX_SIGINT);
goto discard;
}
if (AsciiChar == SavedTermios.c_cc[VSUSP] &&
SavedTermios.c_cc[VSUSP] != _POSIX_VDISABLE) {
SignalSession(PSX_SIGTSTP);
bSignalInterrupt = TRUE;
goto discard;
}
if (AsciiChar == SavedTermios.c_cc[VQUIT] &&
SavedTermios.c_cc[VQUIT] != _POSIX_VDISABLE) {
SignalSession(PSX_SIGQUIT);
bSignalInterrupt = TRUE;
goto discard;
}
}
if (SavedTermios.c_lflag & ICANON) {
if (AsciiChar == CR) {
if ((SavedTermios.c_iflag & ICRNL) &&
!(SavedTermios.c_iflag & IGNCR)) {
AsciiChar = NL;
}
}
if (AsciiChar == NL) {
if (SavedTermios.c_iflag & INLCR) {
AsciiChar = CR;
//XXX.mjb: process this CR?
}
}
if (AsciiChar == SavedTermios.c_cc[VKILL] &&
SavedTermios.c_cc[VKILL] != _POSIX_VDISABLE) {
LineKillFound = TRUE;
goto discard;
}
if (AsciiChar == SavedTermios.c_cc[VERASE] &&
SavedTermios.c_cc[VERASE] != _POSIX_VDISABLE) {
// character erase
BackSpaceFound = TRUE;
goto discard;
}
if (AsciiChar == SavedTermios.c_cc[VEOF] &&
SavedTermios.c_cc[VEOF] != _POSIX_VDISABLE) {
// End of file.
AsciiChar = '\n'; // force end-of-line
InputEOF = TRUE;
goto discard;
}
if (AsciiChar == SavedTermios.c_cc[VEOL] &&
SavedTermios.c_cc[VEOL] != _POSIX_VDISABLE) {
// End of line.
AsciiChar = '\n'; // force end-of-line
}
}
LocalBuf[LocalCnt++] = AsciiChar;
discard:
EnterCriticalSection(&KbdBufMutex);
if (BackSpaceFound) {
BOOLEAN bEchoE = ((SavedTermios.c_lflag & ECHOE) == ECHOE);
BkspcKbdBuffer(bEchoE);
} else if (LineKillFound) {
KillKbdBuffer();
} else {
AddKbdBuffer(LocalBuf, LocalCnt);
}
if (SavedTermios.c_lflag & ECHO) {
TermOutput(hConsoleOutput, LocalBuf, LocalCnt);
}
if (bSignalInterrupt || InputEOF) {
//
// Blocked reads should be unblocked, to return
// either the data that has been accumulated (EOF)
// or EINTR (Signal)
//
SignalInterrupt = bSignalInterrupt;
bSuccess = SetEvent(hCanonEvent);
ASSERT(bSuccess);
bSuccess = SetEvent(hIoEvent);
ASSERT(bSuccess);
bSignalInterrupt = FALSE;
}
if (AsciiChar == '\n' && (SavedTermios.c_lflag & ICANON)
&& ++LineCount == 1) {
//
// If we've just finished a new line, signal any
// readers who were waiting on a complete line.
//
bSuccess = SetEvent(hCanonEvent);
ASSERT(bSuccess);
// Reset the keeping-track of tabs.
NumTabs = 0;
}
LeaveCriticalSection(&KbdBufMutex);
}
}
}