|
|
#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); } } }
|