Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2213 lines
54 KiB

/*++
Copyright (c) 1999-2001 Microsoft Corporation
Module Name:
vtutf8scraper.cpp
Abstract:
Class for performing vtutf8 screen scrapning of a command console shell
Author:
Brian Guarraci (briangu) 2001.
Revision History:
(remotely based on scraper.cpp from telnet code)
--*/
#include <cmnhdr.h>
#include <Scraper.h>
#include <utils.h>
#include "vtutf8scraper.h"
//
// Termcap globals
//
#define CM_STRING_LENGTH 9
LPWSTR lpszCMResultsBuffer = NULL;
//the string is of the form <ESC>[ Ps m
#define SGR_STRING_LENGTH 6
PWCHAR szSGRStr = NULL;
#define SGR( szSGRString, num ) \
wnsprintf( \
szSGRString, \
sizeof(WCHAR) * SGR_STRING_LENGTH, \
L"\033[%dm", \
num \
); \
szSGRString[SGR_STRING_LENGTH-1] = UNICODE_NULL;
//
// Number of seconds before we timeout waiting for
// the next character in the
// <esc>ctrl-a
// <esc>ctrl-c
// <esc>ctrl-u
//
#define ESC_CTRL_SEQUENCE_TIMEOUT (2 * 1000)
//
// Tick Marker for when we get an esc-ctrl-X sequence
//
DWORD TimedEscSequenceTickCount = 0;
//
// Globals for tracking the screen scraping attributes
//
COORD coExpectedCursor = { ~0, ~0 };
BOOL fBold = false;
WORD wExistingAttributes = 0;
WORD wDefaultAttributes = 0;
//
//
//
#define MAX_SIZEOF_SCREEN (m_wMaxRows * m_wMaxCols * sizeof( CHAR_INFO ))
//
//
//
#define DBG_DUMP_SCREEN_INFO()\
{ \
WCHAR blob[256]; \
wsprintf(blob,L"wRows: %d\n", m_wRows); \
OutputDebugString(blob); \
wsprintf(blob,L"wCols: %d\n", m_wCols); \
OutputDebugString(blob); \
wsprintf(blob,L"r*c: %d\n", m_wRows * m_wCols); \
OutputDebugString(blob); \
wsprintf(blob,L"sizeof: %d\n", sizeof(CHAR_INFO)); \
OutputDebugString(blob); \
wsprintf(blob,L"r*c*s: %d\n", m_wRows * m_wCols * sizeof(CHAR_INFO)); \
OutputDebugString(blob); \
wsprintf(blob,L"max: %d\n", MAX_SIZEOF_SCREEN); \
OutputDebugString(blob); \
}
CVTUTF8Scraper::CVTUTF8Scraper()
/*++
Routine Description:
Default constructor - this should not be used.
Arguments:
None
Return Value:
N/A
--*/
{
pLastSeen = NULL;
pCurrent = NULL;
m_hConBufIn = NULL;
m_hConBufOut = NULL;
m_dwInputSequenceState = IP_INIT;
m_dwDigitInTheSeq = 0;
m_readBuffer = NULL;
lpszCMResultsBuffer = NULL;
szSGRStr = NULL;
}
CVTUTF8Scraper::CVTUTF8Scraper(
CIoHandler *IoHandler,
WORD wCols,
WORD wRows
) : CScraper(
IoHandler,
wCols,
wRows
)
/*++
Routine Description:
Constructor - parameterized
Arguments:
IoHandler - IoHandler the scraper should use
wCols - the # of rows the console screen buffer should have
wRows - the # of cols the console screen buffer should have
Return Value:
N/A
--*/
{
pLastSeen = NULL;
pCurrent = NULL;
m_dwInputSequenceState = IP_INIT;
m_dwDigitInTheSeq = 0;
m_readBuffer = new WCHAR[READ_BUFFER_LENGTH];
lpszCMResultsBuffer = new WCHAR[CM_STRING_LENGTH];
szSGRStr = new WCHAR[SGR_STRING_LENGTH];
}
CVTUTF8Scraper::~CVTUTF8Scraper()
/*++
Routine Description:
Destructor
Arguments:
N/A
Return Value:
N/A
--*/
{
//
// release our screen buffers
//
if( pLastSeen )
{
delete[] pLastSeen;
}
if( pCurrent )
{
delete[] pCurrent;
}
if (m_readBuffer) {
delete[] m_readBuffer;
}
if (lpszCMResultsBuffer) {
delete[] lpszCMResultsBuffer;
}
if (szSGRStr) {
delete[] szSGRStr;
}
//
// Parent CScraper closes Con I/O handles
//
NOTHING;
}
void
CVTUTF8Scraper::ResetLastScreen(
VOID
)
/*++
Routine Description:
Reset the memory of the last display
This forces the screen scraper to think that everything
is different, hence it sends a full screen dump
Arguments:
None
Return Value:
None
--*/
{
//
// clear the screen buffers and screen info
//
memset( &LastCSBI, 0, sizeof( LastCSBI ) );
memset( pLastSeen, 0, MAX_SIZEOF_SCREEN );
memset( pCurrent, 0, MAX_SIZEOF_SCREEN );
fBold = false;
wExistingAttributes = 0;
wDefaultAttributes = 0;
}
BOOL
CVTUTF8Scraper::DisplayFullScreen(
VOID
)
/*++
Routine Description:
This routine forces the screen scraper to think that everything
is different, hence it sends a full screen dump
Arguments:
None
Return Value:
TRUE - success
FALSE - otherwise
--*/
{
BOOL bSuccess;
//
// Reset the memory of the last display
//
// This forces the screen scraper to think that everything
// is different, hence it sends a full screen dump
//
ResetLastScreen();
//
// Call the screen scraper
//
bSuccess = Write();
return bSuccess;
}
BOOL
CVTUTF8Scraper::SetScreenBufferInfo(
VOID
)
/*++
Routine Description:
This routine sets the current console screen buffer's
parameters to what we think they should be. The primary
purpose for this routine is to provide a means to change
the screen buffer max X/Y to the m_wCols/m_wRows so that
the output fits in our scraping window.
Arguments:
pCSBI - the current console screen buffer info
Return Value:
TRUE - success
FALSE - otherwise
--*/
{
COORD coordLargest;
COORD coordSize;
//
// Start with our max window size and shrink if we need to
//
m_wCols = m_wMaxCols;
m_wRows = m_wMaxRows;
//
// Get the current window info
//
coordLargest = GetLargestConsoleWindowSize( m_hConBufOut );
if( coordLargest.X < m_wCols && coordLargest.X != 0 )
{
m_wCols = coordLargest.X;
}
if( coordLargest.Y < m_wRows && coordLargest.Y != 0 )
{
m_wRows = coordLargest.Y;
}
//
// make the window the size we think it should be
//
coordSize.X = m_wCols;
coordSize.Y = m_wRows;
SetConsoleScreenBufferSize( CVTUTF8Scraper::m_hConBufOut, coordSize );
return( TRUE );
}
BOOL
CVTUTF8Scraper::SetWindowInfo(
VOID
)
/*++
Routine Description:
This routine sets the initial console window info.
Arguments:
None
Return Value:
TRUE - success
FALSE - otherwise
--*/
{
COORD coordLargest;
SMALL_RECT sr;
COORD coordSize;
CONSOLE_SCREEN_BUFFER_INFO csbi;
//
// Start with our max window size and shrink if we need to
//
m_wCols = m_wMaxCols;
m_wRows = m_wMaxRows;
//
// Get the current window info
//
coordLargest = GetLargestConsoleWindowSize( m_hConBufOut );
if( coordLargest.X < m_wCols && coordLargest.X != 0 )
{
m_wCols = coordLargest.X;
}
if( coordLargest.Y < m_wRows && coordLargest.Y != 0 )
{
m_wRows = coordLargest.Y;
}
//
//
//
sr.Top = 0;
sr.Bottom = ( WORD )( m_wRows - 1 );
sr.Right = ( WORD ) ( m_wCols - 1 );
sr.Left = 0;
//
//
//
coordSize.X = m_wCols;
coordSize.Y = m_wRows;
//
//
//
GetConsoleScreenBufferInfo( CVTUTF8Scraper::m_hConBufOut, &csbi);
// Logic: If the Old Window Size is less than the new Size then we set
// the Screen Buffer Size first and then set the Window Size.
// If the Old Window Size is Greater than the new Size then we set the
// window Size first and then the Screen Buffer.
// The above is because the Buffer Size always has to be greater than or
// equal to the Window Size.
if ( (csbi.dwSize.X < coordSize.X) || (csbi.dwSize.Y < coordSize.Y) )
{
COORD coordTmpSize = { 0, 0 };
coordTmpSize .X = ( csbi.dwSize.X < coordSize.X ) ? coordSize.X : csbi.dwSize.X;
coordTmpSize .Y = ( csbi.dwSize.Y < coordSize.Y ) ? coordSize.Y : csbi.dwSize.Y;
SetConsoleScreenBufferSize ( CVTUTF8Scraper::m_hConBufOut, coordTmpSize );
SetConsoleWindowInfo ( CVTUTF8Scraper::m_hConBufOut, TRUE, &sr );
SetConsoleScreenBufferSize ( CVTUTF8Scraper::m_hConBufOut, coordSize );
}
else
{
SetConsoleWindowInfo( CVTUTF8Scraper::m_hConBufOut, TRUE, &sr );
SetConsoleScreenBufferSize( CVTUTF8Scraper::m_hConBufOut, coordSize );
}
return( TRUE );
}
BOOL
CVTUTF8Scraper::InitScraper(
VOID
)
/*++
Routine Description:
This routine initializes the local scraper configures the console
to be the dimensions that the scraper requires.
Arguments:
None
Return Value:
TRUE - the scraper was initialized
FALSE - otherwise
--*/
{
//
// Configure the console dimensions
//
if( !SetWindowInfo() )
{
return( FALSE );
}
//
// Create and initialize the scraper buffers
//
if( pLastSeen )
{
delete[] pLastSeen;
}
if( pCurrent )
{
delete[] pCurrent;
}
pLastSeen = ( PCHAR_INFO ) new char[MAX_SIZEOF_SCREEN];
pCurrent = ( PCHAR_INFO ) new char[MAX_SIZEOF_SCREEN];
ASSERT(pLastSeen);
ASSERT(pCurrent);
if( !pLastSeen || !pCurrent )
{
return ( FALSE );
}
//
// Initialize the screen buffers and info
//
memset( &LastCSBI, 0, sizeof( LastCSBI ) );
memset( pCurrent, 0, MAX_SIZEOF_SCREEN );
memset( pLastSeen, 0, MAX_SIZEOF_SCREEN );
return( TRUE );
}
BOOL
CVTUTF8Scraper::CreateIOHandle(
IN PWCHAR HandleName,
OUT PHANDLE pHandle
)
/*++
Routine Description:
This routine opens a handle of the specified name.
Note: This routine used to open CONIN$ and CONOUT$ handles
for the console being scraped.
Arguments:
HandleName - the name of the handle to open
pHandle - on success, the resulting handle
Return Value:
TRUE - the handle was created
FALSE - otherwise
--*/
{
BOOL bSuccess;
//
// default: failed to open handle
//
bSuccess = FALSE;
do {
//
// Attempt to open the console input handle
//
*pHandle = CreateFile(
HandleName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
ASSERT( *pHandle != INVALID_HANDLE_VALUE );
if ( *pHandle == INVALID_HANDLE_VALUE) {
break;
}
//
// We were successful
//
bSuccess = TRUE;
} while ( FALSE );
return bSuccess;
}
BOOL
CVTUTF8Scraper::CreateConsoleOutHandle(
VOID
)
/*++
Routine Description:
This routine creates a CONOUT$ handle to the current console screen buffer.
Arguments:
None
Return Value:
TRUE - the handle was created
FALSE - otherwise
--*/
{
BOOL bSuccess;
HANDLE h;
//
// Close the current console out handle
//
if ((m_hConBufOut != NULL) && (m_hConBufOut != INVALID_HANDLE_VALUE)) {
CloseHandle(m_hConBufOut);
}
//
// Create the new console out handle
//
bSuccess = CreateIOHandle(
L"CONOUT$",
&h
);
if (bSuccess) {
SetConOut(h);
}
return bSuccess;
}
BOOL
CVTUTF8Scraper::CreateConsoleInHandle(
VOID
)
/*++
Routine Description:
This routine creates a CONIN$ handle to the current console screen buffer.
Arguments:
None
Return Value:
TRUE - the handle was created
FALSE - otherwise
--*/
{
BOOL bSuccess;
HANDLE h;
//
// Close the current console in handle
//
if ((m_hConBufIn != NULL) && (m_hConBufIn != INVALID_HANDLE_VALUE)) {
CloseHandle(m_hConBufIn);
}
//
// Create the new console out handle
//
bSuccess = CreateIOHandle(
L"CONIN$",
&h
);
if (bSuccess) {
SetConIn(h);
}
return bSuccess;
}
BOOL
CVTUTF8Scraper::CreateIOHandles(
VOID
)
/*++
Routine Description:
This routine creates CONIN$ and CONOUT$ handles to the
current console screen buffer.
Arguments:
None
Return Value:
TRUE - the handles were created
FALSE - otherwise
--*/
{
//
// note: the scraper parent class will reap the std io handles
//
return ((CreateConsoleOutHandle()) && (CreateConsoleInHandle()));
}
BOOL
CVTUTF8Scraper::Start(
VOID
)
/*++
Routine Description:
This routine initializes the scraper and prepares it so
that this routine can be immediately followed by a Write().
Arguments:
None
Return Value:
TRUE - the scraper was started
FALSE - otherwise
--*/
{
BOOL bSuccess;
//
// Create the Console IO Handles
//
bSuccess = CreateIOHandles();
ASSERT_STATUS(bSuccess, FALSE);
//
// Initialize the scraper
//
bSuccess = InitScraper();
ASSERT_STATUS(bSuccess, FALSE);
//
// Reset the last screen display memory
//
ResetLastScreen();
return( TRUE );
}
BOOL
CVTUTF8Scraper::Write(
VOID
)
/*++
Routine Description:
This routine scrapes the current console buffer and writes the emulated
terminal output to the current IoHandler.
Note: In order to guarantee coherency between what the scaper sees and
the apps being scraped, this routine ensures the current screen
buffer is of the expected dimensions the scraper cares about.
If the dimensions are not correct, it forces them to what it expects.
For instance, Edit resizes the window to 80x25, but the scraper may
expect 80x24, so the console buffer gets resized to 80x24.
Arguments:
None
Return Value:
TRUE - the write operation was successful
FALSE - otherwise
Security:
Interface:
Console - we write user input to the console
--*/
{
BOOL bSuccess;
DWORD dwStatus;
//
// Open a handle to the active console screen buffer
//
// Note: This is a necessary step.
// We need to ensure we have a handle to the
// current console screen buffer before we
// attempt to screen scrape.
// The reason we concern ourselves with having
// the current screen buffer handle is because
// apps can use the CreateConsoleScreenBuffer &
// SetConsoleActiveScreenBuffer APIs. These
// APIs effectively invalidate our CONOUT$ until
// they switch back to their original conout handle.
// A typical use scenario is:
//
// 1. get original conout handle:
// PrevConsole = GetStdHandle(STD_OUTPUT_HANDLE);
// 2. create a new console screen buffer
// 3. set new screen buffer as the active one
// 4. do work using new screen buffer...
// 5. set screen buffer to original
//
if ( !CreateConsoleOutHandle() ) {
//Could be an application that does not share its screen buffer
return TRUE;
}
//
// Read the current screen buffer info
//
dwStatus = GetConsoleScreenBufferInfo( m_hConBufOut, &CSBI );
ASSERT( dwStatus );
if( !dwStatus )
{
return ( FALSE );
}
//
// Make sure the screen buffer size hasn't changed.
// if it has, then set it back.
//
if (CSBI.dwMaximumWindowSize.X > m_wCols ||
CSBI.dwMaximumWindowSize.Y > m_wRows
) {
//
// We have detected a change in the screen buffer settings
// this very likely means that an app has created a new
// screen buffer and made it the active one. In order
// to not confuse the app during its initialization phase
// we need to pause for a short period before resetting
// the screen buffer parameters.
//
Sleep(100);
//
//
//
if( !SetScreenBufferInfo() ) {
return( FALSE );
}
//
// reread the current screen buffer info
//
dwStatus = GetConsoleScreenBufferInfo( m_hConBufOut, &CSBI );
ASSERT( dwStatus );
if( !dwStatus )
{
return ( FALSE );
}
}
//
// Perform the screen scraping
//
bSuccess = CompareAndUpdate(
m_wRows,
m_wCols,
pCurrent,
pLastSeen,
&CSBI,
&LastCSBI
);
return bSuccess;
}
BOOL
CVTUTF8Scraper::Read()
/*++
Routine Description:
this routine retrieves user-input from the IoHandler and
sends it to the current console buffer.
Arguments:
None
Return Value:
TRUE - the read operation was successful
FALSE - otherwise
Security:
Inteface:
external input --> internal
--*/
{
DWORD i;
BOOL dwStatus = 0;
DWORD dwCharsTransferred = 0;
ULONG bufferSize;
BOOL bInEnhancedCharSequence;
BOOL bSuccess;
//
// read Channel::stdin
//
bSuccess = m_IoHandler->Read(
(PUCHAR)m_readBuffer,
READ_BUFFER_SIZE,
&bufferSize
);
if (!bSuccess) {
return (FALSE);
}
//
// Determine the # of WCHARs read
//
dwCharsTransferred = bufferSize / sizeof(WCHAR);
//
// Process the read characters
//
for( i=0; i < dwCharsTransferred; i++ ) {
//
// Examine the input stream and parse out any VT-UTF8 escape sequences
// that are present.
//
bInEnhancedCharSequence = ProcessEnhancedKeys( m_readBuffer[i] );
//
// If the last character processed started/continued an
// enhanced key sequence, then continue processing enhanced
// keys
//
if (bInEnhancedCharSequence) {
continue;
}
//
// Handle ctrl-c (this behavior is taken from tlntsess.exe)
//
if( (UCHAR)m_readBuffer[i] == CTRLC ) {
//
//The follwing is the observed behaviour of CTRL C
//When ENABLE_PROCESSED_INPUT mode is not enabled, pass CTRL C as
//input to the console input buffer.
//When ENABLE_PROCESSED_INPUT mode is enabled, generate CTTRL C signal
//and also unblock any ReadConsoleInput. This behaviour is what is observed
// and not from any documentation.
//
DWORD dwMode = 0;
GetConsoleMode( m_hConBufIn, &dwMode );
if( dwMode & ENABLE_PROCESSED_INPUT ) {
GenerateConsoleCtrlEvent( CTRL_C_EVENT, 0);
continue;
}
}
//
// Send the character to the command console
//
dwStatus = WriteAKeyToCMD(m_readBuffer[i]);
if( !dwStatus ) {
return ( FALSE );
}
}
return( TRUE );
}
DWORD
CVTUTF8Scraper::WriteAKeyToCMD(
WCHAR Char
)
/*++
Routine Description:
This routine sends a character to the console.
Arguments:
Char - the character to send
Return Value:
status
--*/
{
DWORD dwStatus;
SHORT vk;
BYTE vkcode;
WORD wScanCode;
//
// default: failure
//
dwStatus = 0;
//
// Get the virtual key scan code for the character
//
vk = VkKeyScan( (UCHAR)Char );
if( vk != 0xffff )
{
DWORD dwShiftcode;
//
// translates (maps) a virtual-key code into a scan code
//
vkcode = LOBYTE( vk );
wScanCode = ( WORD )MapVirtualKey( vkcode, 0 );
//
// determine if any modifiers need to be sent
//
dwShiftcode = 0;
if( HIBYTE( vk ) & 1 ) {
dwShiftcode |= SHIFT_PRESSED;
}
if( HIBYTE( vk ) & 2 ) {
dwShiftcode |= LEFT_CTRL_PRESSED;
}
if( HIBYTE( vk ) & 4 ) {
dwShiftcode |= LEFT_ALT_PRESSED;
}
//
// Send the character and modifiers
//
dwStatus = WriteAKeyToCMD(
TRUE,
vkcode,
wScanCode,
Char,
dwShiftcode
);
}
return dwStatus;
}
DWORD
CVTUTF8Scraper::WriteAKeyToCMD(
WORD wVKCode,
WORD wVSCode,
WCHAR Char,
DWORD dwCKState
)
/*++
Routine Description:
This routine sends a character which has already been scanned
to the console.
Arguments:
wVKCode - the virtual key code
wVSCode - the scan code
Char - the character to send
dwCKState - the control key state
Return Value:
--*/
{
return WriteAKeyToCMD(
TRUE,
wVKCode,
wVSCode,
Char,
dwCKState
);
}
DWORD
CVTUTF8Scraper::WriteAKeyToCMD(
BOOL bKeyDown,
WORD wVKCode,
WORD wVSCode,
WCHAR Char,
DWORD dwCKState
)
/*++
Routine Description:
This routine sends a character which has already been scanned
to the console. In addition, the caller can specify the key
press status of this character.
Arguments:
bKeyDown - the key status of the character
wVKCode - the virtual key code
wVSCode - the scan code
Char - the character to send
dwCKState - the control key state
Return Value:
Status
Security:
external input --> interal
we write data retrieved from remote user to the
cmd console stdin
--*/
{
DWORD dwStatus = 0;
DWORD dwCount = 0;
INPUT_RECORD input;
ZeroMemory( &input, sizeof( INPUT_RECORD ) );
input.EventType = KEY_EVENT;
input.Event.KeyEvent.bKeyDown = bKeyDown;
input.Event.KeyEvent.wRepeatCount = 1;
input.Event.KeyEvent.wVirtualKeyCode = wVKCode;
input.Event.KeyEvent.wVirtualScanCode = wVSCode;
input.Event.KeyEvent.uChar.UnicodeChar = Char;
input.Event.KeyEvent.dwControlKeyState = dwCKState;
dwStatus = WriteConsoleInput( m_hConBufIn, &input, 1, &dwCount );
return dwStatus;
}
BOOL
CVTUTF8Scraper::IsValidControlSequence(
VOID
)
/*++
Routine Description:
Determine how long it has been since the esc-ctrl-a sequence
Arguments:
None
Return Value:
TRUE - the control sequence has occured with the allowable time-frame
FALSE - otherwise
--*/
{
DWORD DeltaT;
DeltaT = GetAndComputeTickCountDeltaT(TimedEscSequenceTickCount);
return (DeltaT <= ESC_CTRL_SEQUENCE_TIMEOUT);
}
BOOL
CVTUTF8Scraper::ProcessEnhancedKeys(
IN WCHAR cCurrentChar
)
/*++
Routine Description:
This routine parse the character stream and determines if there
have been any enhanced key sequences. If so, it removes the sequence
from the character stream and send the key to the console.
Arguments:
cCurrentChar - the current character in the stream
Return Value:
TRUE - The character processed started/continued an enhanced key sequence
FALSE - otherwise
--*/
{
BOOL bRetVal = true;
switch( m_dwInputSequenceState )
{
case IP_INIT:
switch (cCurrentChar) {
case ESC:
//
// We are now in an <esc> sequence
//
m_dwInputSequenceState = IP_ESC_RCVD;
break;
default:
//
// Not any special char
//
bRetVal = false;
break;
}
break;
case IP_ESC_RCVD:
m_dwInputSequenceState = IP_INIT;
//
// Map the VT-UTF8 encodings of the ENHANCED keys
//
switch (cCurrentChar) {
case '[':
m_dwInputSequenceState = IP_ESC_BRACKET_RCVD;
break;
case 'h':
WriteAKeyToCMD( VK_HOME, VS_HOME, 0, ENHANCED_KEY );
break;
case 'k':
WriteAKeyToCMD( VK_END, VS_END, 0, ENHANCED_KEY );
break;
case '+':
WriteAKeyToCMD( VK_INSERT, VS_INSERT, 0, ENHANCED_KEY );
break;
case '-':
WriteAKeyToCMD( VK_DELETE, VS_DELETE, 0, ENHANCED_KEY );
break;
case '?': // page up
WriteAKeyToCMD( VK_PRIOR, VS_PRIOR, 0, ENHANCED_KEY );
break;
case '/': // page down
WriteAKeyToCMD( VK_NEXT, VS_NEXT, 0, ENHANCED_KEY );
break;
case '1': // F1
WriteAKeyToCMD( VK_F1, VK_F1, 0, ENHANCED_KEY );
break;
case '2': // F2
WriteAKeyToCMD( VK_F2, VK_F2, 0, ENHANCED_KEY );
break;
case '3': // F3
WriteAKeyToCMD( VK_F3, VS_F3, 0, ENHANCED_KEY );
break;
case '4': // F4
WriteAKeyToCMD( VK_F4, VS_F4, 0, ENHANCED_KEY );
break;
case '5': // F5
WriteAKeyToCMD( VK_F5, VS_F5, 0, ENHANCED_KEY );
break;
case '6': // F6
WriteAKeyToCMD( VK_F6, VS_F6, 0, ENHANCED_KEY );
break;
case '7': // F7
WriteAKeyToCMD( VK_F7, VS_F7, 0, ENHANCED_KEY );
break;
case '8': // F8
WriteAKeyToCMD( VK_F8, VS_F8, 0, ENHANCED_KEY );
break;
case '9': // F9
WriteAKeyToCMD( VK_F9, VS_F9, 0, ENHANCED_KEY );
break;
case '0': // F10
WriteAKeyToCMD( VK_F10, VS_F10, 0, ENHANCED_KEY );
break;
case '!': // F11
WriteAKeyToCMD( VK_F11, VS_F11, 0, ENHANCED_KEY );
break;
case '@': // F12
WriteAKeyToCMD( VK_F12, VS_F12, 0, ENHANCED_KEY );
break;
case CTRLA:
m_dwInputSequenceState = IP_ESC_CTRL_A_RCVD;
//
// Mark when we received this sequence
//
TimedEscSequenceTickCount = GetTickCount();
break;
case CTRLS:
m_dwInputSequenceState = IP_ESC_CTRL_S_RCVD;
//
// Mark when we received this sequence
//
TimedEscSequenceTickCount = GetTickCount();
break;
case CTRLC:
m_dwInputSequenceState = IP_ESC_CTRL_C_RCVD;
//
// Mark when we received this sequence
//
TimedEscSequenceTickCount = GetTickCount();
break;
default:
//
// Write already received escape as it is and return false
//
WriteAKeyToCMD( VK_ESCAPE, VS_ESCAPE, ESC, ENHANCED_KEY );
bRetVal = false;
break;
}
break;
case IP_ESC_BRACKET_RCVD:
m_dwInputSequenceState = IP_INIT;
switch( cCurrentChar )
{
case 'A':
WriteAKeyToCMD( VK_UP, VS_UP, 0, ENHANCED_KEY );
break;
case 'B':
WriteAKeyToCMD( VK_DOWN, VS_DOWN, 0, ENHANCED_KEY );
break;
case 'C':
WriteAKeyToCMD( VK_RIGHT, VS_RIGHT, 0, ENHANCED_KEY );
break;
case 'D':
WriteAKeyToCMD( VK_LEFT, VS_LEFT, 0, ENHANCED_KEY );
break;
default:
//
// Send the <esc>[ characters through since a valid sequence
// was not recognized
//
WriteAKeyToCMD( VK_ESCAPE, VS_ESCAPE, ESC, ENHANCED_KEY );
WriteAKeyToCMD( VS_LEFT_BRACKET, VS_LEFT_BRACKET, '[', 0 );
//
// Not any special char
//
bRetVal = false;
break;
}
break;
case IP_ESC_CTRL_A_ESC_RCVD:
m_dwInputSequenceState = IP_INIT;
switch (cCurrentChar) {
case CTRLA:
//
// If we arrived here within 2 seconds
// then we should process the current character as an alt sequence
// otherwise, there is nothing to do
//
if (IsValidControlSequence()) {
//
// Send: <alt-pressed><alt-released>
//
// Normally this should be benign, but some apps may
// respond to this - for instance move the user to the menu
// bar
//
WriteAKeyToCMD( TRUE, VK_MENU, VS_MENU, 0, ENHANCED_KEY );
WriteAKeyToCMD( FALSE, VK_MENU, VS_MENU, 0, ENHANCED_KEY );
break;
}
//
// if the <esc><ctrl-a><esc><ctrl-a> sequenced timed-out,
// then fall through and do the default behavior.
//
default:
//
// We either timed-out after the <esc><ctrl-a><esc>
// or we received the following sequence:
//
// <esc><ctrl-a><esc>X
//
// We know that the <esc><ctrl-a><esc> was valid because
// we arrived here. Hence, in either case the translation
// should be:
//
// <alt-esc>X
//
WriteAKeyToCMD( TRUE, VK_MENU, VS_MENU, 0, ENHANCED_KEY );
WriteAKeyToCMD(ESC);
WriteAKeyToCMD( FALSE, VK_MENU, VS_MENU, 0, ENHANCED_KEY );
//
// Send the current character (X) through to be processed normally
//
bRetVal = false;
break;
}
break;
case IP_ESC_CTRL_A_RCVD:
m_dwInputSequenceState = IP_INIT;
switch (cCurrentChar) {
case ESC:
//
// If we arrived here within 2 seconds of receiving the ctrl-a
// then we should process the current character as an alt sequence
// otherwise, there is nothing to do
//
if (IsValidControlSequence()) {
//
// We need to move to the <esc><ctrl-a><esc> state
//
m_dwInputSequenceState = IP_ESC_CTRL_A_ESC_RCVD;
//
// Mark when we received this sequence
//
TimedEscSequenceTickCount = GetTickCount();
} else {
//Not any special char
bRetVal = false;
}
break;
default:
//
// If we arrived here within 2 seconds of receiving the ctrl-a
// then we should process the current character as an alt sequence
// otherwise, there is nothing to do
//
if (IsValidControlSequence()) {
WriteAKeyToCMD( TRUE, VK_MENU, VS_MENU, 0, ENHANCED_KEY );
WriteAKeyToCMD(cCurrentChar);
WriteAKeyToCMD( FALSE, VK_MENU, VS_MENU, 0, ENHANCED_KEY );
} else {
//Not any special char
bRetVal = false;
}
break;
}
break;
case IP_ESC_CTRL_C_RCVD:
m_dwInputSequenceState = IP_INIT;
//
// If we arrived here within 2 seconds of receiving the ctrl-c
// then we should process the current character as an alt sequence
// otherwise, there is nothing to do
//
if (IsValidControlSequence()) {
WriteAKeyToCMD( TRUE, VK_CONTROL, VS_CONTROL, 0, ENHANCED_KEY );
WriteAKeyToCMD(cCurrentChar);
WriteAKeyToCMD( FALSE, VK_CONTROL, VS_CONTROL, 0, ENHANCED_KEY );
} else {
//Not any special char
bRetVal = false;
}
break;
case IP_ESC_CTRL_S_RCVD:
m_dwInputSequenceState = IP_INIT;
//
// If we arrived here within 2 seconds of receiving the ctrl-c
// then we should process the current character as an alt sequence
// otherwise, there is nothing to do
//
if (IsValidControlSequence()) {
WriteAKeyToCMD( TRUE, VK_SHIFT, VS_SHIFT, 0, ENHANCED_KEY );
WriteAKeyToCMD(cCurrentChar);
WriteAKeyToCMD( FALSE, VK_SHIFT, VS_SHIFT, 0, ENHANCED_KEY );
} else {
//Not any special char
bRetVal = false;
}
break;
default:
//Should not happen
ASSERT( 0 );
}
return bRetVal;
}
BOOL
CVTUTF8Scraper::SendBytes(
PUCHAR pucBuf,
DWORD dwLength
)
/*++
Routine Description:
This routine sends an array of bytes to the IoHandler.
Arguments:
pucBuf - the array to send
dwLength - the # of bytes to send
Return Value:
Status
Security:
internal --> external
we are sending internal data to remote user
--*/
{
ASSERT(pucBuf);
return m_IoHandler->Write(
pucBuf,
dwLength
);
}
BOOL
CVTUTF8Scraper::SendString(
PWCHAR pwch
)
/*++
Routine Description:
This routine sends a WCHAR string to the IoHandler.
Arguments:
pwch - the string to send
Return Value:
Status
--*/
{
ASSERT(pwch);
return SendBytes(
( PUCHAR )pwch,
(ULONG)(wcslen(pwch) * sizeof(WCHAR))
);
}
BOOL
CVTUTF8Scraper::SendColorInfo(
WORD wAttributes
)
/*++
Routine Description:
This routine assembles a VT-UTF8 encoded color attibutes command
and sends it to the IoHandler
Arguments:
wAttributes - the attributes to encode
Return Value:
Status
--*/
{
BOOL bSuccess;
//
// default
//
bSuccess = FALSE;
do {
if( wAttributes & BACKGROUND_INTENSITY )
{
//do nothing.
//There is no equivalent capability on vtutf8
NOTHING;
}
if( wAttributes & FOREGROUND_INTENSITY )
{
if( !fBold )
{
SGR( szSGRStr, 1 ); //Bold
bSuccess = SendString( szSGRStr );
if (! bSuccess) {
break;
}
fBold = true;
}
}
else
{
if( fBold )
{
SGR( szSGRStr, 22 ); //Bold off
bSuccess = SendString( szSGRStr );
if (! bSuccess) {
break;
}
fBold = false;
}
}
WORD wColor = 0;
if( wAttributes & FOREGROUND_BLUE )
{
wColor = ( WORD )( wColor | 0x0004 );
}
if( wAttributes & FOREGROUND_GREEN )
{
wColor = ( WORD )( wColor | 0x0002 );
}
if( wAttributes & FOREGROUND_RED )
{
wColor = ( WORD )( wColor | 0x0001 );
}
wColor += 30; //Base value for foreground colors
SGR( szSGRStr, wColor );
bSuccess = SendString( szSGRStr );
if (! bSuccess) {
break;
}
//WORD wColor = 0;
wColor = 0;
if( wAttributes & BACKGROUND_BLUE )
{
wColor = ( WORD )( wColor | 0x0004 );
}
if( wAttributes & BACKGROUND_GREEN )
{
wColor = ( WORD )( wColor | 0x0002 );
}
if( wAttributes & BACKGROUND_RED )
{
wColor = ( WORD )( wColor | 0x0001 );
}
wColor += 40; //Base value for Background colors
SGR( szSGRStr, wColor );
bSuccess = SendString( szSGRStr );
} while ( FALSE );
return bSuccess;
}
#define COMPARE_ROWS(currentRow, lastSeenRow, result) \
for(i = 0; i < wCols; ++i ) \
{ \
if( pCurrent[ ( currentRow ) * wCols + i].Char.UnicodeChar != \
pLastSeen[ ( lastSeenRow ) * wCols + i].Char.UnicodeChar ) \
{\
(result) = 0; \
break;\
} \
if( ( wDefaultAttributes != pCurrent[ ( currentRow ) * wCols + i]. \
Attributes ) && \
( pCurrent[ ( currentRow ) * wCols + i].Attributes != \
pLastSeen[ ( lastSeenRow ) * wCols + i].Attributes ) ) \
{ \
(result) = 0; \
break; \
} \
}
//row, column are over the wire should be w.r.t screen.
//So, +1 for both row, column
#define POSITION_CURSOR( row, column ) \
ASSERT(row <= 23); \
{ \
CursorMove( \
lpszCMResultsBuffer, \
( WORD ) ( ( row ) + 1 ), \
( WORD ) ( ( column ) + 1 ) \
); \
bSuccess = SendString( lpszCMResultsBuffer ); \
if (!bSuccess) { \
break; \
} \
}
//
// Send columns [begin -- end] characters on <row>
//
// Note: Because we are modeling a unicode console, we have to be careful about
// what we decide to represent as vtutf8. Characters that take up more than
// one screen cell in the console have to be processed so we don't send
// redundant data.
//
// Hence, we only send a character if the cell contains:
//
// 1. a single byte character
// 2. the first position of a unicode character
// You can tell the first character position of a unicode characeter
// because the cells are enumarated. The enumeration is determined by:
//
// enum = (CHAR_INFO.Attributes & 0x0000ff00) >> 8
//
#if 0
//
// Very noisy debug version
//
#define SEND_ROW( row, begin, end ) \
{ \
CHAR_INFO chi; \
UCHAR x; \
WCHAR blob[256]; \
wsprintf(blob,L"\r\n(row=%d:begin=%d:end=%d)\r\n", row, begin, end); \
OutputDebugString(blob); \
for(LONG c = ( begin ); c < ( end ); ++c ) { \
if( wExistingAttributes != pCurrent[( row ) * wCols + c].Attributes ) { \
wExistingAttributes = pCurrent[ ( row ) * wCols + c].Attributes; \
wDefaultAttributes = ( WORD )~0; \
bSuccess = SendColorInfo( wExistingAttributes ); \
if (!bSuccess) { \
break; \
} \
wsprintf(blob,L"(Color:%x)", wExistingAttributes); \
OutputDebugString(blob); \
} \
chi = pCurrent[ ( row ) * wCols + c]; \
x = (UCHAR)((chi.Attributes & 0x0000ff00) >> 8); \
wsprintf(blob,L"(%x)", chi.Char.UnicodeChar); \
OutputDebugString(blob); \
if (x < 2) { \
bSuccess = SendChar( chi.Char.UnicodeChar ); \
if (!bSuccess) { \
break; \
} \
} \
} \
}
#else
#define SEND_ROW( row, begin, end ) \
{ \
CHAR_INFO chi; \
UCHAR x; \
for(LONG c = ( begin ); c < ( end ); ++c ) { \
if( wExistingAttributes != pCurrent[( row ) * wCols + c].Attributes ) { \
wExistingAttributes = pCurrent[ ( row ) * wCols + c].Attributes; \
wDefaultAttributes = ( WORD )~0; \
bSuccess = SendColorInfo( wExistingAttributes ); \
if (!bSuccess) { \
break; \
} \
} \
chi = pCurrent[ ( row ) * wCols + c]; \
x = (UCHAR)((chi.Attributes & 0x0000ff00) >> 8); \
if (x < 2) { \
bSuccess = SendChar( chi.Char.UnicodeChar ); \
if (!bSuccess) { \
break; \
} \
} \
} \
}
#endif
#define GET_DEFAULT_COLOR \
if( wDefaultAttributes == 0 ) \
{ \
wDefaultAttributes = pCurrent[ 0 ].Attributes; \
wExistingAttributes = pCurrent[ 0 ].Attributes; \
}
#define IS_BLANK( row, col ) \
( pCurrent[ ( row ) * wCols + ( col ) ].Char.UnicodeChar == ' ' )
#define IS_DIFFERENT_COLOR( row, col, attribs ) \
( pCurrent[ ( row ) * wCols + ( col ) ].Attributes != ( attribs ) )
#define IS_CHANGE_IN_COLOR( row, col ) \
( pCurrent[ ( row ) * wCols + ( col ) ].Attributes != \
pLastSeen[ ( row ) * wCols + ( col ) ].Attributes )
#define IS_CHANGE_IN_CHAR( row, col ) \
( pCurrent[ ( row ) * wCols + ( col ) ].Char.UnicodeChar != \
pLastSeen[ ( row ) * wCols + ( col )].Char.UnicodeChar )
BOOL
CVTUTF8Scraper::CompareAndUpdate(
WORD wRows,
WORD wCols,
PCHAR_INFO pCurrent,
PCHAR_INFO pLastSeen,
PCONSOLE_SCREEN_BUFFER_INFO pCSBI,
PCONSOLE_SCREEN_BUFFER_INFO pLastCSBI
)
/*++
Routine Description:
This routine does the core work for scraping the screen.
Algorithm:
This routine does a row-by-row comparision.
If a row is found to be different, it figures out which region
of the row is different and sends that sub-row piece.
Arguments:
wRows - the # of rows to scrape
wCols - the # of cols to scrape
pCurrent - the current scraper buffer
pLastSeen - the last scraper buffer
pCSBI - the current Console Screen Buffer Info
pLastCSBI - the current Console Screen Buffer Info
Return Value:
TRUE - success
FALSE - otherwise
--*/
{
INT i;
WORD wRow;
WORD wCol;
INT iStartCol;
INT iEndCol;
BOOL fBlankLine;
COORD coordDest;
COORD coordOrigin;
SMALL_RECT srSource;
BOOL DifferenceFound;
BOOL bSuccess;
//
// default: we succeeded
//
bSuccess = TRUE;
//
// Default: no difference found
//
DifferenceFound = false;
//
//
//
GET_DEFAULT_COLOR;
//
// Read the console character matrix
//
ASSERT(wCols <= m_wMaxCols);
ASSERT(wRows <= m_wMaxRows);
coordDest.X = wCols;
coordDest.Y = wRows;
coordOrigin.X = 0;
coordOrigin.Y = 0;
srSource.Left = 0;
srSource.Top = 0;
srSource.Right = ( WORD ) ( wCols - 1 );
srSource.Bottom = ( WORD ) ( wRows - 1 );
bSuccess = ReadConsoleOutput(
m_hConBufOut,
pCurrent,
coordDest,
coordOrigin,
&srSource
);
if( !bSuccess )
{
return ( FALSE );
}
//
// Search the current and last screen buffers for differences.
//
wRow = wCol = 0;
while ( wRow < wRows ) {
//
// Compare the current row (wRow)
//
if( memcmp( &pCurrent[wRow * wCols],
&pLastSeen[wRow * wCols],
wCols * sizeof( CHAR_INFO ) ) != 0
) {
//
// A difference was found
//
DifferenceFound = true;
//
// Initialize the difference tracking markers
//
iStartCol = -1;
iEndCol = -1;
fBlankLine = true;
//
// Determine where in the current row the rows differ
//
for (i = 0 ; i < wCols; ++i ) {
if( IS_DIFFERENT_COLOR( wRow, i, wDefaultAttributes ) &&
IS_CHANGE_IN_COLOR( wRow, i )
) {
if( iStartCol == -1 )
{
iStartCol = i;
}
iEndCol = i;
fBlankLine = false;
}
if( IS_CHANGE_IN_CHAR( wRow, i ) ) {
if( iStartCol == -1 ) {
iStartCol = i;
}
iEndCol = i;
}
if( fBlankLine && !IS_BLANK( wRow, i ) ) {
fBlankLine = false;
}
}
if( fBlankLine ) {
POSITION_CURSOR( wRow, 0 );
CursorEOL();
coExpectedCursor.Y = wRow;
coExpectedCursor.X = 0;
} else if( iStartCol != -1 ) {
if( wRow != coExpectedCursor.Y || iStartCol != coExpectedCursor.X ) {
POSITION_CURSOR( wRow, iStartCol );
coExpectedCursor.X = ( SHORT )iStartCol;
coExpectedCursor.Y = wRow;
}
SEND_ROW( wRow, iStartCol, iEndCol+1 );
coExpectedCursor.X = ( SHORT ) ( coExpectedCursor.X + iEndCol - iStartCol + 1 );
}
}
++wRow;
}
//
// If we found a difference while doing the screen compares
// or if the cursor moved in the console,
// then update the cursor position
//
if( DifferenceFound ||
( memcmp( &pCSBI->dwCursorPosition, &pLastCSBI->dwCursorPosition, sizeof( COORD ) ) != 0 )
) {
do {
//
// Move the cursor to where it's supposed to be
//
POSITION_CURSOR(
pCSBI->dwCursorPosition.Y,
pCSBI->dwCursorPosition.X
);
coExpectedCursor.X = pCSBI->dwCursorPosition.X;
coExpectedCursor.Y = pCSBI->dwCursorPosition.Y;
//
// Copy pCurrent onto pLastSeen
//
memcpy( pLastSeen, pCurrent, wCols * wRows * sizeof( CHAR_INFO ) );
memcpy( pLastCSBI, pCSBI, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
//
// There is a difference between this screen and the last,
// so we have written the changes. Now that we are done
// writing the changes, we need to flush the data we've written.
//
m_IoHandler->Flush();
} while ( FALSE );
}
return( bSuccess );
}
LPWSTR
CVTUTF8Scraper::CursorMove(
OUT LPWSTR pCmsResult,
IN WORD y,
IN WORD x
)
/*++
Routine Description:
This routine assembles an Ansi escape sequence to position
the cursor on a Ansi terminal
Arguments:
lpCmsResult - on exit, buffer contains the string
y - the Y cursor position
x - the X cursor position
Return Value:
the pointer into the result buffer at the NULL
--*/
{
#if DBG
PWCHAR pBegin;
pBegin = pCmsResult;
#endif
ASSERT(pCmsResult);
//
// Assemble the prefix sequence prefix
//
pCmsResult[0] = 0x1B; // <esc>
pCmsResult[1] = L'[';
pCmsResult++;
pCmsResult++;
//
// Translate the Y position
//
// 1 or 2 characters consumed
//
pCmsResult = FastIToA_10(
y,
pCmsResult
);
//
// Insert the delimiter
//
*pCmsResult = L';';
pCmsResult++;
//
// Translate the X position
//
// 1 or 2 characters consumed
//
pCmsResult = FastIToA_10(
x,
pCmsResult
);
//
// Insert the suffix
//
*pCmsResult = L'H';
pCmsResult++;
//
// Terminate the string
//
*pCmsResult = UNICODE_NULL;
//
// make sure we have a valid string length
//
ASSERT(wcslen(pBegin) <= CM_STRING_LENGTH - 1);
return ( pCmsResult );
}