/*++ Copyright (c) 1985 - 1999, Microsoft Corporation Module Name: input.c Abstract: This file implements the circular buffer management for input events. The circular buffer is described by a header, which resides in the beginning of the memory allocated when the buffer is created. The header contains all of the per-buffer information, such as reader, writer, and reference counts, and also holds the pointers into the circular buffer proper. When the in and out pointers are equal, the circular buffer is empty. When the in pointer trails the out pointer by 1, the buffer is full. Thus, a 512 byte buffer can hold only 511 bytes; one byte is lost so that full and empty conditions can be distinguished. So that the user can put 512 bytes in a buffer that they created with a size of 512, we allow for this byte lost when allocating the memory. Author: Therese Stowell (thereses) 6-Nov-1990 Adapted from OS/2 subsystem server\srvpipe.c Revision History: --*/ #include "precomp.h" #pragma hdrstop #define CTRL_BUT_NOT_ALT(n) \ (((n) & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) && \ !((n) & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))) UINT ProgmanHandleMessage; int DialogBoxCount; LPTHREAD_START_ROUTINE CtrlRoutine; // address of client side ctrl-thread routine DWORD InputThreadTlsIndex; #define MAX_CHARS_FROM_1_KEYSTROKE 6 // // the following data structures are a hack to work around the fact that // MapVirtualKey does not return the correct virtual key code in many cases. // we store the correct info (from the keydown message) in the CONSOLE_KEY_INFO // structure when a keydown message is translated. then when we receive a // wm_[sys][dead]char message, we retrieve it and clear out the record. // #define CONSOLE_FREE_KEY_INFO 0 #define CONSOLE_MAX_KEY_INFO 32 typedef struct _CONSOLE_KEY_INFO { HWND hWnd; WORD wVirtualKeyCode; WORD wVirtualScanCode; } CONSOLE_KEY_INFO, *PCONSOLE_KEY_INFO; CONSOLE_KEY_INFO ConsoleKeyInfo[CONSOLE_MAX_KEY_INFO]; VOID UserExitWorkerThread(NTSTATUS Status); BOOL InitWindowClass( VOID ); #if !defined(FE_SB) NTSTATUS ReadBuffer( IN PINPUT_INFORMATION InputInformation, OUT PVOID Buffer, IN ULONG Length, OUT PULONG EventsRead, IN BOOL Peek, IN BOOL StreamRead, OUT PBOOL ResetWaitEvent ); #endif NTSTATUS CreateInputBuffer( IN ULONG NumberOfEvents OPTIONAL, IN PINPUT_INFORMATION InputBufferInformation #if defined(FE_SB) , IN PCONSOLE_INFORMATION Console #endif ) /*++ Routine Description: This routine creates an input buffer. It allocates the circular buffer and initializes the information fields. Arguments: NumberOfEvents - Size of input buffer in events. InputBufferInformation - Pointer to input buffer information structure. Return Value: --*/ { ULONG BufferSize; NTSTATUS Status; if (NumberOfEvents == 0) { NumberOfEvents = DEFAULT_NUMBER_OF_EVENTS; } // allocate memory for circular buffer BufferSize = sizeof(INPUT_RECORD) * (NumberOfEvents+1); InputBufferInformation->InputBuffer = ConsoleHeapAlloc(BUFFER_TAG, BufferSize); if (InputBufferInformation->InputBuffer == NULL) { return STATUS_NO_MEMORY; } Status = NtCreateEvent(&InputBufferInformation->InputWaitEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); if (!NT_SUCCESS(Status)) { ConsoleHeapFree(InputBufferInformation->InputBuffer); return STATUS_NO_MEMORY; } InitializeListHead(&InputBufferInformation->ReadWaitQueue); // initialize buffer header InputBufferInformation->InputBufferSize = NumberOfEvents; InputBufferInformation->ShareAccess.OpenCount = 0; InputBufferInformation->ShareAccess.Readers = 0; InputBufferInformation->ShareAccess.Writers = 0; InputBufferInformation->ShareAccess.SharedRead = 0; InputBufferInformation->ShareAccess.SharedWrite = 0; InputBufferInformation->InputMode = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT; InputBufferInformation->RefCount = 0; InputBufferInformation->First = (ULONG_PTR) InputBufferInformation->InputBuffer; InputBufferInformation->In = (ULONG_PTR) InputBufferInformation->InputBuffer; InputBufferInformation->Out = (ULONG_PTR) InputBufferInformation->InputBuffer; InputBufferInformation->Last = (ULONG_PTR) InputBufferInformation->InputBuffer + BufferSize; #if defined(FE_SB) #if defined(FE_IME) InputBufferInformation->ImeMode.Disable = FALSE; InputBufferInformation->ImeMode.Unavailable = FALSE; InputBufferInformation->ImeMode.Open = FALSE; InputBufferInformation->ImeMode.ReadyConversion = FALSE; #endif // FE_IME InputBufferInformation->Console = Console; RtlZeroMemory(&InputBufferInformation->ReadConInpDbcsLeadByte,sizeof(INPUT_RECORD)); RtlZeroMemory(&InputBufferInformation->WriteConInpDbcsLeadByte,sizeof(INPUT_RECORD)); #endif return STATUS_SUCCESS; } NTSTATUS ReinitializeInputBuffer( OUT PINPUT_INFORMATION InputBufferInformation ) /*++ Routine Description: This routine resets the input buffer information fields to their initial values. Arguments: InputBufferInformation - Pointer to input buffer information structure. Return Value: Note: The console lock must be held when calling this routine. --*/ { NtClearEvent(InputBufferInformation->InputWaitEvent); InputBufferInformation->ShareAccess.OpenCount = 0; InputBufferInformation->ShareAccess.Readers = 0; InputBufferInformation->ShareAccess.Writers = 0; InputBufferInformation->ShareAccess.SharedRead = 0; InputBufferInformation->ShareAccess.SharedWrite = 0; InputBufferInformation->InputMode = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT; InputBufferInformation->In = (ULONG_PTR) InputBufferInformation->InputBuffer; InputBufferInformation->Out = (ULONG_PTR) InputBufferInformation->InputBuffer; return STATUS_SUCCESS; } VOID FreeInputBuffer( IN PINPUT_INFORMATION InputBufferInformation ) /*++ Routine Description: This routine frees the resources associated with an input buffer. Arguments: InputBufferInformation - Pointer to input buffer information structure. Return Value: --*/ { UserAssert(InputBufferInformation->RefCount == 0); CloseHandle(InputBufferInformation->InputWaitEvent); ConsoleHeapFree(InputBufferInformation->InputBuffer); } NTSTATUS WaitForMoreToRead( IN PINPUT_INFORMATION InputInformation, IN PCSR_API_MSG Message OPTIONAL, IN CSR_WAIT_ROUTINE WaitRoutine OPTIONAL, IN PVOID WaitParameter OPTIONAL, IN ULONG WaitParameterLength OPTIONAL, IN BOOLEAN WaitBlockExists OPTIONAL ) /*++ Routine Description: This routine waits for a writer to add data to the buffer. Arguments: InputInformation - buffer to wait for Console - Pointer to console buffer information. Message - if called from dll (not InputThread), points to api message. this parameter is used for wait block processing. WaitRoutine - Routine to call when wait is woken up. WaitParameter - Parameter to pass to wait routine. WaitParameterLength - Length of wait parameter. WaitBlockExists - TRUE if wait block has already been created. Return Value: STATUS_WAIT - call was from client and wait block has been created. STATUS_SUCCESS - call was from server and wait has been satisfied. --*/ { PVOID WaitParameterBuffer; if (!WaitBlockExists) { WaitParameterBuffer = ConsoleHeapAlloc(WAIT_TAG, WaitParameterLength); if (WaitParameterBuffer == NULL) { return STATUS_NO_MEMORY; } RtlCopyMemory(WaitParameterBuffer,WaitParameter,WaitParameterLength); #if defined(FE_SB) if (WaitParameterLength == sizeof(COOKED_READ_DATA) && InputInformation->Console->lpCookedReadData == WaitParameter) { InputInformation->Console->lpCookedReadData = WaitParameterBuffer; } #endif if (!CsrCreateWait(&InputInformation->ReadWaitQueue, WaitRoutine, CSR_SERVER_QUERYCLIENTTHREAD(), Message, WaitParameterBuffer)) { ConsoleHeapFree(WaitParameterBuffer); #if defined(FE_SB) InputInformation->Console->lpCookedReadData = NULL; #endif return STATUS_NO_MEMORY; } } return CONSOLE_STATUS_WAIT; } VOID WakeUpReadersWaitingForData( IN PCONSOLE_INFORMATION Console, PINPUT_INFORMATION InputInformation ) /*++ Routine Description: This routine wakes up readers waiting for data to read. Arguments: InputInformation - buffer to alert readers for Return Value: TRUE - The operation was successful FALSE/NULL - The operation failed. --*/ { BOOLEAN WaitSatisfied; WaitSatisfied = CsrNotifyWait(&InputInformation->ReadWaitQueue, FALSE, NULL, NULL ); if (WaitSatisfied) { // #334370 under stress, WaitQueue may already hold the satisfied waits UserAssert((Console->WaitQueue == NULL) || (Console->WaitQueue == &InputInformation->ReadWaitQueue)); Console->WaitQueue = &InputInformation->ReadWaitQueue; } } NTSTATUS GetNumberOfReadyEvents( IN PINPUT_INFORMATION InputInformation, OUT PULONG NumberOfEvents ) /*++ Routine Description: This routine returns the number of events in the input buffer. Arguments: InputInformation - Pointer to input buffer information structure. NumberOfEvents - On output contains the number of events. Return Value: Note: The console lock must be held when calling this routine. --*/ { if (InputInformation->In < InputInformation->Out) { *NumberOfEvents = (ULONG)(InputInformation->Last - InputInformation->Out); *NumberOfEvents += (ULONG)(InputInformation->In - InputInformation->First); } else { *NumberOfEvents = (ULONG)(InputInformation->In - InputInformation->Out); } *NumberOfEvents /= sizeof(INPUT_RECORD); return STATUS_SUCCESS; } NTSTATUS FlushAllButKeys( PINPUT_INFORMATION InputInformation ) /*++ Routine Description: This routine removes all but the key events from the buffer. Arguments: InputInformation - Pointer to input buffer information structure. Return Value: Note: The console lock must be held when calling this routine. --*/ { ULONG NumberOfEventsRead,i; NTSTATUS Status; PINPUT_RECORD TmpInputBuffer,InPtr,TmpInputBufferPtr; ULONG BufferSize; BOOL Dummy; if (InputInformation->In != InputInformation->Out) { // // allocate memory for temp buffer // BufferSize = sizeof(INPUT_RECORD) * (InputInformation->InputBufferSize+1); TmpInputBuffer = ConsoleHeapAlloc(TMP_TAG, BufferSize); if (TmpInputBuffer == NULL) { return STATUS_NO_MEMORY; } TmpInputBufferPtr = TmpInputBuffer; // // copy input buffer. // let ReadBuffer do any compaction work. // Status = ReadBuffer(InputInformation, TmpInputBuffer, InputInformation->InputBufferSize, &NumberOfEventsRead, TRUE, FALSE, &Dummy #if defined(FE_SB) , TRUE #endif ); if (!NT_SUCCESS(Status)) { ConsoleHeapFree(TmpInputBuffer); return Status; } InputInformation->Out = (ULONG_PTR) InputInformation->InputBuffer; InPtr = InputInformation->InputBuffer; for (i=0;iEventType == KEY_EVENT) { *InPtr = *TmpInputBuffer; InPtr++; } TmpInputBuffer++; } InputInformation->In = (ULONG_PTR) InPtr; if (InputInformation->In == InputInformation->Out) { NtClearEvent(InputInformation->InputWaitEvent); } ConsoleHeapFree(TmpInputBufferPtr); } return STATUS_SUCCESS; } NTSTATUS FlushInputBuffer( PINPUT_INFORMATION InputInformation ) /*++ Routine Description: This routine empties the input buffer Arguments: InputInformation - Pointer to input buffer information structure. Return Value: Note: The console lock must be held when calling this routine. --*/ { InputInformation->In = (ULONG_PTR) InputInformation->InputBuffer; InputInformation->Out = (ULONG_PTR) InputInformation->InputBuffer; NtClearEvent(InputInformation->InputWaitEvent); return STATUS_SUCCESS; } NTSTATUS SetInputBufferSize( IN PINPUT_INFORMATION InputInformation, IN ULONG Size ) /*++ Routine Description: This routine resizes the input buffer. Arguments: InputInformation - Pointer to input buffer information structure. Size - New size in number of events. Return Value: Note: The console lock must be held when calling this routine. --*/ { ULONG NumberOfEventsRead; NTSTATUS Status; PINPUT_RECORD InputBuffer; ULONG BufferSize; BOOL Dummy; #if DBG ULONG_PTR NumberOfEvents; if (InputInformation->In < InputInformation->Out) { NumberOfEvents = InputInformation->Last - InputInformation->Out; NumberOfEvents += InputInformation->In - InputInformation->First; } else { NumberOfEvents = InputInformation->In - InputInformation->Out; } NumberOfEvents /= sizeof(INPUT_RECORD); #endif UserAssert(Size > InputInformation->InputBufferSize); // // Allocate memory for new input buffer. // BufferSize = sizeof(INPUT_RECORD) * (Size+1); InputBuffer = ConsoleHeapAlloc(BUFFER_TAG, BufferSize); if (InputBuffer == NULL) { return STATUS_NO_MEMORY; } // // Copy old input buffer. Let the ReadBuffer do any compaction work. // Status = ReadBuffer(InputInformation, InputBuffer, Size, &NumberOfEventsRead, TRUE, FALSE, &Dummy #if defined(FE_SB) , TRUE #endif ); if (!NT_SUCCESS(Status)) { ConsoleHeapFree(InputBuffer); return Status; } InputInformation->Out = (ULONG_PTR)InputBuffer; InputInformation->In = (ULONG_PTR)InputBuffer + sizeof(INPUT_RECORD) * NumberOfEventsRead; // // adjust pointers // InputInformation->First = (ULONG_PTR) InputBuffer; InputInformation->Last = (ULONG_PTR) InputBuffer + BufferSize; // // free old input buffer // ConsoleHeapFree(InputInformation->InputBuffer); InputInformation->InputBufferSize = Size; InputInformation->InputBuffer = InputBuffer; return Status; } NTSTATUS ReadBuffer( IN PINPUT_INFORMATION InputInformation, OUT PVOID Buffer, IN ULONG Length, OUT PULONG EventsRead, IN BOOL Peek, IN BOOL StreamRead, OUT PBOOL ResetWaitEvent #ifdef FE_SB , IN BOOLEAN Unicode #endif ) /*++ Routine Description: This routine reads from a buffer. It does the actual circular buffer manipulation. Arguments: InputInformation - buffer to read from Buffer - buffer to read into Length - length of buffer in events EventsRead - where to store number of events read Peek - if TRUE, don't remove data from buffer, just copy it. StreamRead - if TRUE, events with repeat counts > 1 are returned as multiple events. also, EventsRead == 1. ResetWaitEvent - on exit, TRUE if buffer became empty. Return Value: ?? Note: The console lock must be held when calling this routine. --*/ { ULONG TransferLength,OldTransferLength; ULONG BufferLengthInBytes; #ifdef FE_SB PCONSOLE_INFORMATION Console; ULONG Length2; PINPUT_RECORD BufferRecords; PINPUT_RECORD QueueRecords; WCHAR UniChar; WORD EventType; #endif #ifdef FE_SB Console = InputInformation->Console; #endif *ResetWaitEvent = FALSE; // // if StreamRead, just return one record. if repeat count is greater // than one, just decrement it. the repeat count is > 1 if more than // one event of the same type was merged. we need to expand them back // to individual events here. // if (StreamRead && ((PINPUT_RECORD)(InputInformation->Out))->EventType == KEY_EVENT) { UserAssert(Length == 1); UserAssert(InputInformation->In != InputInformation->Out); RtlMoveMemory((PBYTE)Buffer, (PBYTE)InputInformation->Out, sizeof(INPUT_RECORD) ); InputInformation->Out += sizeof(INPUT_RECORD); if (InputInformation->Last == InputInformation->Out) { InputInformation->Out = InputInformation->First; } if (InputInformation->Out == InputInformation->In) { *ResetWaitEvent = TRUE; } *EventsRead = 1; return STATUS_SUCCESS; } BufferLengthInBytes = Length * sizeof(INPUT_RECORD); // // if in > out, buffer looks like this: // // out in // ______ _____________ // | | | | // | free | data | free | // |______|______|______| // // we transfer the requested number of events or the amount in the buffer // if (InputInformation->In > InputInformation->Out) { if ((InputInformation->In - InputInformation->Out) > BufferLengthInBytes) { TransferLength = BufferLengthInBytes; } else { TransferLength = (ULONG)(InputInformation->In - InputInformation->Out); } #ifdef FE_SB if (!Unicode) { BufferLengthInBytes = 0; OldTransferLength = TransferLength / sizeof(INPUT_RECORD); BufferRecords = (PINPUT_RECORD)Buffer; QueueRecords = (PINPUT_RECORD)InputInformation->Out; while (BufferLengthInBytes < Length && OldTransferLength) { UniChar = QueueRecords->Event.KeyEvent.uChar.UnicodeChar; EventType = QueueRecords->EventType; *BufferRecords++ = *QueueRecords++; if (EventType == KEY_EVENT) { if (IsConsoleFullWidth(Console->hDC, Console->CP, UniChar)) { BufferLengthInBytes += 2; } else { BufferLengthInBytes++; } } else { BufferLengthInBytes++; } OldTransferLength--; } UserAssert(TransferLength >= OldTransferLength * sizeof(INPUT_RECORD)); TransferLength -= OldTransferLength * sizeof(INPUT_RECORD); } else #endif { RtlMoveMemory((PBYTE)Buffer, (PBYTE)InputInformation->Out, TransferLength ); } *EventsRead = TransferLength / sizeof(INPUT_RECORD); #ifdef FE_SB UserAssert(*EventsRead <= Length); #endif if (!Peek) { InputInformation->Out += TransferLength; #ifdef FE_SB UserAssert(InputInformation->Out <= InputInformation->Last); #endif } if (InputInformation->Out == InputInformation->In) { *ResetWaitEvent = TRUE; } return STATUS_SUCCESS; } // // if out > in, buffer looks like this: // // in out // ______ _____________ // | | | | // | data | free | data | // |______|______|______| // // we read from the out pointer to the end of the buffer then from the // beginning of the buffer, until we hit the in pointer or enough bytes // are read. // else { if ((InputInformation->Last - InputInformation->Out) > BufferLengthInBytes) { TransferLength = BufferLengthInBytes; } else { TransferLength = (ULONG)(InputInformation->Last - InputInformation->Out); } #ifdef FE_SB if (!Unicode) { BufferLengthInBytes = 0; OldTransferLength = TransferLength / sizeof(INPUT_RECORD); BufferRecords = (PINPUT_RECORD)Buffer; QueueRecords = (PINPUT_RECORD)InputInformation->Out; while (BufferLengthInBytes < Length && OldTransferLength) { UniChar = QueueRecords->Event.KeyEvent.uChar.UnicodeChar; EventType = QueueRecords->EventType; *BufferRecords++ = *QueueRecords++; if (EventType == KEY_EVENT) { if (IsConsoleFullWidth(Console->hDC, Console->CP, UniChar)) { BufferLengthInBytes += 2; } else { BufferLengthInBytes++; } } else { BufferLengthInBytes++; } OldTransferLength--; } UserAssert(TransferLength >= OldTransferLength * sizeof(INPUT_RECORD)); TransferLength -= OldTransferLength * sizeof(INPUT_RECORD); } else #endif { RtlMoveMemory((PBYTE)Buffer, (PBYTE)InputInformation->Out, TransferLength ); } *EventsRead = TransferLength / sizeof(INPUT_RECORD); #ifdef FE_SB UserAssert(*EventsRead <= Length); #endif if (!Peek) { InputInformation->Out += TransferLength; #ifdef FE_SB UserAssert(InputInformation->Out <= InputInformation->Last); #endif if (InputInformation->Out == InputInformation->Last) { InputInformation->Out = InputInformation->First; } } #ifdef FE_SB if (!Unicode) { if (BufferLengthInBytes >= Length) { if (InputInformation->Out == InputInformation->In) { *ResetWaitEvent = TRUE; } return STATUS_SUCCESS; } } else #endif if (*EventsRead == Length) { if (InputInformation->Out == InputInformation->In) { *ResetWaitEvent = TRUE; } return STATUS_SUCCESS; } // // hit end of buffer, read from beginning // OldTransferLength = TransferLength; #ifdef FE_SB Length2 = Length; if (!Unicode) { UserAssert(Length > BufferLengthInBytes); Length -= BufferLengthInBytes; if (Length == 0) { if (InputInformation->Out == InputInformation->In) { *ResetWaitEvent = TRUE; } return STATUS_SUCCESS; } BufferLengthInBytes = Length * sizeof(INPUT_RECORD); if ((InputInformation->In - InputInformation->First) > BufferLengthInBytes) { TransferLength = BufferLengthInBytes; } else { TransferLength = (ULONG)(InputInformation->In - InputInformation->First); } } else #endif if ((InputInformation->In - InputInformation->First) > (BufferLengthInBytes - OldTransferLength)) { TransferLength = BufferLengthInBytes - OldTransferLength; } else { TransferLength = (ULONG)(InputInformation->In - InputInformation->First); } #ifdef FE_SB if (!Unicode) { BufferLengthInBytes = 0; OldTransferLength = TransferLength / sizeof(INPUT_RECORD); QueueRecords = (PINPUT_RECORD)InputInformation->First; while (BufferLengthInBytes < Length && OldTransferLength) { UniChar = QueueRecords->Event.KeyEvent.uChar.UnicodeChar; EventType = QueueRecords->EventType; *BufferRecords++ = *QueueRecords++; if (EventType == KEY_EVENT) { if (IsConsoleFullWidth(Console->hDC, Console->CP, UniChar)) { BufferLengthInBytes += 2; } else { BufferLengthInBytes++; } } else { BufferLengthInBytes++; } OldTransferLength--; } UserAssert(TransferLength >= OldTransferLength * sizeof(INPUT_RECORD)); TransferLength -= OldTransferLength * sizeof(INPUT_RECORD); } else #endif { RtlMoveMemory((PBYTE)Buffer+OldTransferLength, (PBYTE)InputInformation->First, TransferLength ); } *EventsRead += TransferLength / sizeof(INPUT_RECORD); #ifdef FE_SB UserAssert(*EventsRead <= Length2); #endif if (!Peek) { InputInformation->Out = InputInformation->First + TransferLength; } if (InputInformation->Out == InputInformation->In) { *ResetWaitEvent = TRUE; } return STATUS_SUCCESS; } } NTSTATUS ReadInputBuffer( IN PINPUT_INFORMATION InputInformation, OUT PINPUT_RECORD lpBuffer, IN OUT PDWORD nLength, IN BOOL Peek, IN BOOL WaitForData, IN BOOL StreamRead, IN PCONSOLE_INFORMATION Console, IN PHANDLE_DATA HandleData OPTIONAL, IN PCSR_API_MSG Message OPTIONAL, IN CSR_WAIT_ROUTINE WaitRoutine OPTIONAL, IN PVOID WaitParameter OPTIONAL, IN ULONG WaitParameterLength OPTIONAL, IN BOOLEAN WaitBlockExists OPTIONAL #if defined(FE_SB) , IN BOOLEAN Unicode #endif ) /*++ Routine Description: This routine reads from the input buffer. Arguments: InputInformation - Pointer to input buffer information structure. lpBuffer - Buffer to read into. nLength - On input, number of events to read. On output, number of events read. Peek - If TRUE, copy events to lpBuffer but don't remove them from the input buffer. WaitForData - if TRUE, wait until an event is input. if FALSE, return immediately StreamRead - if TRUE, events with repeat counts > 1 are returned as multiple events. also, EventsRead == 1. Console - Pointer to console buffer information. HandleData - Pointer to handle data structure. This parameter is optional if WaitForData is false. Message - if called from dll (not InputThread), points to api message. this parameter is used for wait block processing. WaitRoutine - Routine to call when wait is woken up. WaitParameter - Parameter to pass to wait routine. WaitParameterLength - Length of wait parameter. WaitBlockExists - TRUE if wait block has already been created. Return Value: Note: The console lock must be held when calling this routine. --*/ { ULONG EventsRead; NTSTATUS Status; BOOL ResetWaitEvent; if (InputInformation->In == InputInformation->Out) { if (!WaitForData) { *nLength = 0; return STATUS_SUCCESS; } LockReadCount(HandleData); HandleData->InputReadData->ReadCount += 1; UnlockReadCount(HandleData); Status = WaitForMoreToRead(InputInformation, Message, WaitRoutine, WaitParameter, WaitParameterLength, WaitBlockExists ); if (!NT_SUCCESS(Status)) { if (Status != CONSOLE_STATUS_WAIT) { /* * WaitForMoreToRead failed, restore ReadCount and bale out */ LockReadCount(HandleData); HandleData->InputReadData->ReadCount -= 1; UnlockReadCount(HandleData); } *nLength = 0; return Status; } // // we will only get to this point if we were called by GetInput. // UserAssert(FALSE); // I say we never get here ! IANJA LockConsole(Console); } // // read from buffer // Status = ReadBuffer(InputInformation, lpBuffer, *nLength, &EventsRead, Peek, StreamRead, &ResetWaitEvent #if defined(FE_SB) , Unicode #endif ); if (ResetWaitEvent) { NtClearEvent(InputInformation->InputWaitEvent); } *nLength = EventsRead; return Status; } NTSTATUS WriteBuffer( OUT PINPUT_INFORMATION InputInformation, IN PVOID Buffer, IN ULONG Length, OUT PULONG EventsWritten, OUT PBOOL SetWaitEvent ) /*++ Routine Description: This routine writes to a buffer. It does the actual circular buffer manipulation. Arguments: InputInformation - buffer to write to Buffer - buffer to write from Length - length of buffer in events BytesRead - where to store number of bytes written. SetWaitEvent - on exit, TRUE if buffer became non-empty. Return Value: ERROR_BROKEN_PIPE - no more readers. Note: The console lock must be held when calling this routine. --*/ { NTSTATUS Status; ULONG TransferLength; ULONG BufferLengthInBytes; #if defined(FE_SB) PCONSOLE_INFORMATION Console = InputInformation->Console; #endif *SetWaitEvent = FALSE; // // windows sends a mouse_move message each time a window is updated. // coalesce these. // if (Length == 1 && InputInformation->Out != InputInformation->In) { PINPUT_RECORD InputEvent=Buffer; if (InputEvent->EventType == MOUSE_EVENT && InputEvent->Event.MouseEvent.dwEventFlags == MOUSE_MOVED) { PINPUT_RECORD LastInputEvent; if (InputInformation->In == InputInformation->First) { LastInputEvent = (PINPUT_RECORD) (InputInformation->Last - sizeof(INPUT_RECORD)); } else { LastInputEvent = (PINPUT_RECORD) (InputInformation->In - sizeof(INPUT_RECORD)); } if (LastInputEvent->EventType == MOUSE_EVENT && LastInputEvent->Event.MouseEvent.dwEventFlags == MOUSE_MOVED) { LastInputEvent->Event.MouseEvent.dwMousePosition.X = InputEvent->Event.MouseEvent.dwMousePosition.X; LastInputEvent->Event.MouseEvent.dwMousePosition.Y = InputEvent->Event.MouseEvent.dwMousePosition.Y; *EventsWritten = 1; return STATUS_SUCCESS; } } else if (InputEvent->EventType == KEY_EVENT && InputEvent->Event.KeyEvent.bKeyDown) { PINPUT_RECORD LastInputEvent; if (InputInformation->In == InputInformation->First) { LastInputEvent = (PINPUT_RECORD) (InputInformation->Last - sizeof(INPUT_RECORD)); } else { LastInputEvent = (PINPUT_RECORD) (InputInformation->In - sizeof(INPUT_RECORD)); } #if defined(FE_SB) if (IsConsoleFullWidth(Console->hDC, Console->CP,InputEvent->Event.KeyEvent.uChar.UnicodeChar)) { ; } else if (InputEvent->Event.KeyEvent.dwControlKeyState & NLS_IME_CONVERSION) { if (LastInputEvent->EventType == KEY_EVENT && LastInputEvent->Event.KeyEvent.bKeyDown && (LastInputEvent->Event.KeyEvent.uChar.UnicodeChar == InputEvent->Event.KeyEvent.uChar.UnicodeChar) && (LastInputEvent->Event.KeyEvent.dwControlKeyState == InputEvent->Event.KeyEvent.dwControlKeyState) ) { LastInputEvent->Event.KeyEvent.wRepeatCount += InputEvent->Event.KeyEvent.wRepeatCount; *EventsWritten = 1; return STATUS_SUCCESS; } } else #endif if (LastInputEvent->EventType == KEY_EVENT && LastInputEvent->Event.KeyEvent.bKeyDown && (LastInputEvent->Event.KeyEvent.wVirtualScanCode == // scancode same InputEvent->Event.KeyEvent.wVirtualScanCode) && (LastInputEvent->Event.KeyEvent.uChar.UnicodeChar == // character same InputEvent->Event.KeyEvent.uChar.UnicodeChar) && (LastInputEvent->Event.KeyEvent.dwControlKeyState == // ctrl/alt/shift state same InputEvent->Event.KeyEvent.dwControlKeyState) ) { LastInputEvent->Event.KeyEvent.wRepeatCount += InputEvent->Event.KeyEvent.wRepeatCount; *EventsWritten = 1; return STATUS_SUCCESS; } } } BufferLengthInBytes = Length*sizeof(INPUT_RECORD); *EventsWritten = 0; while (*EventsWritten < Length) { // // // if out > in, buffer looks like this: // // in out // ______ _____________ // | | | | // | data | free | data | // |______|______|______| // // we can write from in to out-1 // if (InputInformation->Out > InputInformation->In) { TransferLength = BufferLengthInBytes; if ((InputInformation->Out - InputInformation->In - sizeof(INPUT_RECORD)) < BufferLengthInBytes) { Status = SetInputBufferSize(InputInformation, InputInformation->InputBufferSize+Length+INPUT_BUFFER_SIZE_INCREMENT); if (!NT_SUCCESS(Status)) { RIPMSG1(RIP_WARNING, "Couldn't grow input buffer, Status == 0x%x", Status); TransferLength = (ULONG)(InputInformation->Out - InputInformation->In - sizeof(INPUT_RECORD)); if (TransferLength == 0) { return Status; } } else { goto OutPath; // after resizing, in > out } } RtlMoveMemory((PBYTE)InputInformation->In, (PBYTE)Buffer, TransferLength ); Buffer = (PVOID) (((PBYTE) Buffer)+TransferLength); *EventsWritten += TransferLength/sizeof(INPUT_RECORD); BufferLengthInBytes -= TransferLength; InputInformation->In += TransferLength; } // // if in >= out, buffer looks like this: // // out in // ______ _____________ // | | | | // | free | data | free | // |______|______|______| // // we write from the in pointer to the end of the buffer then from the // beginning of the buffer, until we hit the out pointer or enough bytes // are written. // else { if (InputInformation->Out == InputInformation->In) { *SetWaitEvent = TRUE; } OutPath: if ((InputInformation->Last - InputInformation->In) > BufferLengthInBytes) { TransferLength = BufferLengthInBytes; } else { if (InputInformation->First == InputInformation->Out && InputInformation->In == (InputInformation->Last-sizeof(INPUT_RECORD))) { TransferLength = BufferLengthInBytes; Status = SetInputBufferSize(InputInformation, InputInformation->InputBufferSize+Length+INPUT_BUFFER_SIZE_INCREMENT); if (!NT_SUCCESS(Status)) { RIPMSG1(RIP_WARNING, "Couldn't grow input buffer, Status == 0x%x", Status); return Status; } } else { TransferLength = (ULONG)(InputInformation->Last - InputInformation->In); if (InputInformation->First == InputInformation->Out) { TransferLength -= sizeof(INPUT_RECORD); } } } RtlMoveMemory((PBYTE)InputInformation->In, (PBYTE)Buffer, TransferLength ); Buffer = (PVOID) (((PBYTE) Buffer)+TransferLength); *EventsWritten += TransferLength/sizeof(INPUT_RECORD); BufferLengthInBytes -= TransferLength; InputInformation->In += TransferLength; if (InputInformation->In == InputInformation->Last) { InputInformation->In = InputInformation->First; } } if (TransferLength == 0) { UserAssert(FALSE); } } return STATUS_SUCCESS; } __inline BOOL IsSystemKey( WORD wVirtualKeyCode ) { switch (wVirtualKeyCode) { case VK_SHIFT: case VK_CONTROL: case VK_MENU: case VK_PAUSE: case VK_CAPITAL: case VK_LWIN: case VK_RWIN: case VK_NUMLOCK: case VK_SCROLL: return TRUE; } return FALSE; } DWORD PreprocessInput( IN PCONSOLE_INFORMATION Console, IN PINPUT_RECORD InputEvent, IN DWORD nLength ) /*++ Routine Description: This routine processes special characters in the input stream. Arguments: Console - Pointer to console structure. InputEvent - Buffer to write from. nLength - Number of events to write. Return Value: Number of events to write after special characters have been stripped. Note: The console lock must be held when calling this routine. --*/ { ULONG NumEvents; for (NumEvents = nLength; NumEvents != 0; NumEvents--) { if (InputEvent->EventType == KEY_EVENT && InputEvent->Event.KeyEvent.bKeyDown) { // // if output is suspended, any keyboard input releases it. // if ((Console->Flags & CONSOLE_SUSPENDED) && !IsSystemKey(InputEvent->Event.KeyEvent.wVirtualKeyCode)) { UnblockWriteConsole(Console, CONSOLE_OUTPUT_SUSPENDED); RtlMoveMemory(InputEvent, InputEvent + 1, (NumEvents - 1) * sizeof(INPUT_RECORD)); nLength--; continue; } // // intercept control-s // if ((Console->InputBuffer.InputMode & ENABLE_LINE_INPUT) && (InputEvent->Event.KeyEvent.wVirtualKeyCode == VK_PAUSE || IsPauseKey(&InputEvent->Event.KeyEvent))) { Console->Flags |= CONSOLE_OUTPUT_SUSPENDED; RtlMoveMemory(InputEvent, InputEvent + 1, (NumEvents - 1) * sizeof(INPUT_RECORD)); nLength--; continue; } } InputEvent++; } return nLength; } DWORD PrependInputBuffer( IN PCONSOLE_INFORMATION Console, IN PINPUT_INFORMATION InputInformation, IN PINPUT_RECORD lpBuffer, IN DWORD nLength ) /*++ Routine Description: This routine writes to the beginning of the input buffer. Arguments: InputInformation - Pointer to input buffer information structure. lpBuffer - Buffer to write from. nLength - On input, number of events to write. On output, number of events written. Return Value: Note: The console lock must be held when calling this routine. --*/ { NTSTATUS Status; ULONG EventsWritten,EventsRead; BOOL SetWaitEvent; ULONG NumExistingEvents; PINPUT_RECORD pExistingEvents; BOOL Dummy; nLength = PreprocessInput(Console, lpBuffer, nLength); if (nLength == 0) { return 0; } Status = GetNumberOfReadyEvents(InputInformation, &NumExistingEvents ); if (NumExistingEvents) { pExistingEvents = ConsoleHeapAlloc(BUFFER_TAG, NumExistingEvents * sizeof(INPUT_RECORD)); if (pExistingEvents == NULL) return (DWORD)STATUS_NO_MEMORY; Status = ReadBuffer(InputInformation, pExistingEvents, NumExistingEvents, &EventsRead, FALSE, FALSE, &Dummy #if defined(FE_SB) , TRUE #endif ); if (!NT_SUCCESS(Status)) { ConsoleHeapFree(pExistingEvents); return Status; } } else { pExistingEvents = NULL; } // // write new info to buffer // Status = WriteBuffer(InputInformation, lpBuffer, nLength, &EventsWritten, &SetWaitEvent ); // // write existing info to buffer // if (pExistingEvents) { Status = WriteBuffer(InputInformation, pExistingEvents, EventsRead, &EventsWritten, &Dummy ); ConsoleHeapFree(pExistingEvents); } if (SetWaitEvent) { NtSetEvent(InputInformation->InputWaitEvent,NULL); } // // alert any writers waiting for space // WakeUpReadersWaitingForData(Console,InputInformation); return nLength; } DWORD WriteInputBuffer( IN PCONSOLE_INFORMATION Console, IN PINPUT_INFORMATION InputInformation, IN PINPUT_RECORD lpBuffer, IN DWORD nLength ) /*++ Routine Description: This routine writes to the input buffer. Arguments: InputInformation - Pointer to input buffer information structure. lpBuffer - Buffer to write from. nLength - On input, number of events to write. On output, number of events written. Return Value: Note: The console lock must be held when calling this routine. --*/ { ULONG EventsWritten; BOOL SetWaitEvent; nLength = PreprocessInput(Console, lpBuffer, nLength); if (nLength == 0) { return 0; } // // write to buffer // WriteBuffer(InputInformation, lpBuffer, nLength, &EventsWritten, &SetWaitEvent ); if (SetWaitEvent) { NtSetEvent(InputInformation->InputWaitEvent,NULL); } // // alert any writers waiting for space // WakeUpReadersWaitingForData(Console,InputInformation); return EventsWritten; } VOID StoreKeyInfo( IN PMSG msg) { int i; for (i=0;ihwnd) { break; } } if (i!=CONSOLE_MAX_KEY_INFO) { ConsoleKeyInfo[i].hWnd = msg->hwnd; ConsoleKeyInfo[i].wVirtualKeyCode = LOWORD(msg->wParam); ConsoleKeyInfo[i].wVirtualScanCode = (BYTE)(HIWORD(msg->lParam)); } else { RIPMSG0(RIP_WARNING, "ConsoleKeyInfo buffer is full"); } } VOID RetrieveKeyInfo( IN HWND hWnd, OUT PWORD pwVirtualKeyCode, OUT PWORD pwVirtualScanCode, IN BOOL FreeKeyInfo) { int i; for (i = 0; i < CONSOLE_MAX_KEY_INFO; i++) { if (ConsoleKeyInfo[i].hWnd == hWnd) { break; } } if (i != CONSOLE_MAX_KEY_INFO) { *pwVirtualKeyCode = ConsoleKeyInfo[i].wVirtualKeyCode; *pwVirtualScanCode = ConsoleKeyInfo[i].wVirtualScanCode; if (FreeKeyInfo) { ConsoleKeyInfo[i].hWnd = CONSOLE_FREE_KEY_INFO; } } else { *pwVirtualKeyCode = (WORD)MapVirtualKey(*pwVirtualScanCode, 3); } } VOID ClearKeyInfo( IN HWND hWnd ) { int i; for (i=0;iwParam, &pConsole))) { // // Make sure the console doesn't already have a window. // if (pConsole->hWnd) { RIPMSG1(RIP_WARNING, "Console %#p already has a window", pConsole); UnlockConsole(pConsole); return; } pConsole->InputThreadInfo = TlsGetValue(InputThreadTlsIndex); Status = CreateWindowsWindow(pConsole); switch (Status) { case STATUS_SUCCESS: case STATUS_NO_MEMORY: UnlockConsole(pConsole); break; case STATUS_INVALID_HANDLE: // Console is gone, don't do anything. break; default: RIPMSG1(RIP_ERROR, "CreateWindowsWindow returned %x", Status); break; } } } LRESULT DialogHookProc( int nCode, WPARAM wParam, LPARAM lParam ) // this routine gets called to filter input to console dialogs so // that we can do the special processing that StoreKeyInfo does. { MSG *pmsg = (PMSG)lParam; UNREFERENCED_PARAMETER(wParam); if (pmsg->message == CM_CREATE_CONSOLE_WINDOW) { ProcessCreateConsoleWindow(pmsg); return TRUE; } if (CONSOLE_IS_IME_ENABLED()) { if (pmsg->message == CM_CONSOLE_INPUT_THREAD_MSG) { PINPUT_THREAD_INFO pThreadInfo = TlsGetValue(InputThreadTlsIndex); MSG msg; UserAssert(pThreadInfo); if (UnqueueThreadMessage(pThreadInfo->ThreadId, &msg.message, &msg.wParam, &msg.lParam)) { RIPMSG3(RIP_WARNING, "DialogHookProc: %04x (%08x, %08x)", msg.message, msg.wParam, msg.lParam); switch (msg.message) { case CM_CONIME_CREATE: ProcessCreateConsoleIME(&msg, pThreadInfo->ThreadId); return TRUE; case CM_WAIT_CONIME_PROCESS: WaitConsoleIMEStuff((HDESK)msg.wParam, (HANDLE)msg.lParam); return TRUE; case CM_SET_CONSOLEIME_WINDOW: pThreadInfo->hWndConsoleIME = (HWND)msg.wParam; return TRUE; default: RIPMSG1(RIP_WARNING, "DialogHookProc: invalid thread message(%04x) !!", msg.message); break; } } else { RIPMSG0(RIP_WARNING, "DialogHookProc: bogus thread message is posted. ignored"); } } } if (nCode == MSGF_DIALOGBOX) { if (pmsg->message >= WM_KEYFIRST && pmsg->message <= WM_KEYLAST) { if (pmsg->message != WM_CHAR && pmsg->message != WM_DEADCHAR && pmsg->message != WM_SYSCHAR && pmsg->message != WM_SYSDEADCHAR) { // don't store key info if dialog box input if (GetWindowLongPtr(pmsg->hwnd, GWLP_HWNDPARENT) == 0) { StoreKeyInfo(pmsg); } } } } return 0; } #undef DbgPrint // Need this to build on free systems ULONG InputExceptionFilter( PEXCEPTION_POINTERS pexi) { NTSTATUS Status; SYSTEM_KERNEL_DEBUGGER_INFORMATION KernelDebuggerInfo; if (pexi->ExceptionRecord->ExceptionCode != STATUS_PORT_DISCONNECTED) { Status = NtQuerySystemInformation( SystemKernelDebuggerInformation, &KernelDebuggerInfo, sizeof(KernelDebuggerInfo), NULL ); if (NT_SUCCESS(Status) && KernelDebuggerInfo.KernelDebuggerEnabled) { DbgPrint("Unhandled Exception hit in csrss.exe InputExceptionFilter\n"); DbgPrint("first, enter .exr %p for the exception record\n", pexi->ExceptionRecord); DbgPrint("next, enter .cxr %p for the context\n", pexi->ContextRecord); DbgPrint("then kb to get the faulting stack\n"); DbgBreakPoint(); } } return EXCEPTION_EXECUTE_HANDLER; } ///////////////////////////////////////// // Input Thread internal Message Queue: // Mainly used for Console IME stuff ///////////////////////////////////////// LIST_ENTRY gInputThreadMsg; CRITICAL_SECTION gInputThreadMsgLock; VOID InitializeThreadMessages() { RtlEnterCriticalSection(&gInputThreadMsgLock); InitializeListHead(&gInputThreadMsg); RtlLeaveCriticalSection(&gInputThreadMsgLock); } VOID CleanupInputThreadMessages( DWORD dwThreadId) { UINT message; WPARAM wParam; LPARAM lParam; UserAssert(dwThreadId); while (UnqueueThreadMessage(dwThreadId, &message, &wParam, &lParam)) { RIPMSG3(RIP_WARNING, "CleanupInputThreadMessages: %04x (%08x, %08x)", message, wParam, lParam); } } // // QueueThreadMessage // // Posts a message to Input Thread, specified by dwThreadId. // CM_CONSOLE_INPUT_THEAD_MSG is used as a stub message. Actual parameters are // stored in gInputThreadMsg. Input thread should call UnqueueThreadMessage // when it gets CM_CONSOLE_INPUT_THREAD_MSG. // NTSTATUS QueueThreadMessage( DWORD dwThreadId, UINT message, WPARAM wParam, LPARAM lParam) { PCONSOLE_THREAD_MSG pConMsg; RIPMSG4(RIP_VERBOSE, "QueueThreadMessage: TID=%08x msg:%04x (%08x, %08x)", dwThreadId, message, wParam, lParam); pConMsg = ConsoleHeapAlloc(TMP_TAG, sizeof *pConMsg); if (pConMsg == NULL) { RIPMSG0(RIP_WARNING, "QueueThreadMessage: failed to allocate pConMsg"); return STATUS_NO_MEMORY; } pConMsg->dwThreadId = dwThreadId; pConMsg->Message = message; pConMsg->wParam = wParam; pConMsg->lParam = lParam; RtlEnterCriticalSection(&gInputThreadMsgLock); InsertHeadList(&gInputThreadMsg, &pConMsg->ListLink); RtlLeaveCriticalSection(&gInputThreadMsgLock); if (!PostThreadMessage(dwThreadId, CM_CONSOLE_INPUT_THREAD_MSG, 0, 0)) { RIPMSG1(RIP_WARNING, "QueueThreadMessage: failed to post thread msg(%04x)", message); RtlEnterCriticalSection(&gInputThreadMsgLock); RemoveEntryList(&pConMsg->ListLink); RtlLeaveCriticalSection(&gInputThreadMsgLock); ConsoleHeapFree(pConMsg); return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; } // // UnqueueThreadMessage // // return value: // TRUE -- a message found. // FALSE -- no message for dwThreadId found. // BOOL UnqueueThreadMessage( DWORD dwThreadId, UINT* pMessage, WPARAM* pwParam, LPARAM* plParam) { BOOL fResult = FALSE; // if message is found, set this to TRUE PLIST_ENTRY pEntry; UserAssert(dwThreadId); RtlEnterCriticalSection(&gInputThreadMsgLock); // // Search for dwThreadId message from the tail of the queue. // pEntry = gInputThreadMsg.Blink; while (pEntry != &gInputThreadMsg) { PCONSOLE_THREAD_MSG pConMsg = CONTAINING_RECORD(pEntry, CONSOLE_THREAD_MSG, ListLink); if (pConMsg->dwThreadId == dwThreadId) { *pMessage = pConMsg->Message; *pwParam = pConMsg->wParam; *plParam = pConMsg->lParam; RemoveEntryList(pEntry); ConsoleHeapFree(pConMsg); fResult = TRUE; break; } pEntry = pEntry->Blink; } RtlLeaveCriticalSection(&gInputThreadMsgLock); return fResult; } NTSTATUS ConsoleInputThread( PINPUT_THREAD_INIT_INFO pInputThreadInitInfo) { MSG msg; PTEB Teb = NtCurrentTeb(); PCSR_THREAD pcsrt = NULL; INPUT_THREAD_INFO ThreadInfo; int i; HANDLE hThread = NULL; HHOOK hhook = NULL; BOOL fQuit = FALSE; CONSOLEDESKTOPCONSOLETHREAD ConsoleDesktopInfo; NTSTATUS Status; /* * Set this thread's desktop to the one we just created/opened. * When the very first app is loaded, the desktop hasn't been * created yet so the above call might fail. Make sure we don't * accidentally call SetThreadDesktop with a NULL pdesk. The * first app will create the desktop and open it for itself. */ ThreadInfo.Desktop = pInputThreadInitInfo->DesktopHandle; ThreadInfo.WindowCount = 0; ThreadInfo.ThreadHandle = pInputThreadInitInfo->ThreadHandle; ThreadInfo.ThreadId = HandleToUlong(Teb->ClientId.UniqueThread); #if defined(FE_IME) ThreadInfo.hWndConsoleIME = NULL; #endif TlsSetValue(InputThreadTlsIndex, &ThreadInfo); ConsoleDesktopInfo.hdesk = pInputThreadInitInfo->DesktopHandle; ConsoleDesktopInfo.dwThreadId = HandleToUlong(Teb->ClientId.UniqueThread); Status = NtUserConsoleControl(ConsoleDesktopConsoleThread, &ConsoleDesktopInfo, sizeof(ConsoleDesktopInfo)); if (NT_SUCCESS(Status)) { // // This call forces the client-side desktop information // to be updated. // pcsrt = CsrConnectToUser(); if (pcsrt == NULL || !SetThreadDesktop(pInputThreadInitInfo->DesktopHandle)) { Status = STATUS_UNSUCCESSFUL; } else { // // Save our thread handle for cleanup purposes // hThread = pcsrt->ThreadHandle; if (!fOneTimeInitialized) { InitializeCustomCP(); // // Initialize default screen dimensions. we have to initialize // the font info here (in the input thread) so that GDI doesn't // get completely confused on process termination (since a // process that looks like it's terminating created all the // server HFONTS). // EnumerateFonts(EF_DEFFACE); InitializeScreenInfo(); if (!InitWindowClass()) { /* * If the class already exists, this means that some other console attempted * to initialize and registered the class but failed right after. */ if (GetLastError() == ERROR_CLASS_ALREADY_EXISTS) { RIPMSG0(RIP_WARNING, "ConsoleInputThread: Class already exists."); } else { RIPMSG0(RIP_WARNING, "ConsoleInputThread: InitWindowClass failed."); Status = STATUS_UNSUCCESSFUL; } } for (i = 0; i < CONSOLE_MAX_KEY_INFO; i++) { ConsoleKeyInfo[i].hWnd = CONSOLE_FREE_KEY_INFO; } ProgmanHandleMessage = RegisterWindowMessage(TEXT(CONSOLE_PROGMAN_HANDLE_MESSAGE)); } } hhook = SetWindowsHookEx(WH_MSGFILTER, DialogHookProc, NULL, HandleToUlong(Teb->ClientId.UniqueThread)); if (hhook == NULL) { DWORD dwError = GetLastError(); /* * Unfortunately, there's no way to map a Win32 error code to an * NTSTATUS, so let's try to be smart about the most likely reasons * this API would fail. */ RIPMSGF1(RIP_WARNING, "SetWindowsHookEx failed, GLE: 0x%x.", dwError); if (dwError == ERROR_NOT_ENOUGH_MEMORY || dwError == ERROR_OUTOFMEMORY) { Status = STATUS_NO_MEMORY; } else if (dwError == ERROR_ACCESS_DENIED) { Status = STATUS_ACCESS_DENIED; } else { Status = STATUS_UNSUCCESSFUL; } } } // // If we successfully initialized, the input thread is ready to run. // Otherwise, kill the thread. // pInputThreadInitInfo->InitStatus = Status; NtSetEvent(pInputThreadInitInfo->InitCompleteEventHandle, NULL); if (!NT_SUCCESS(Status)) { RIPMSGF1(RIP_WARNING, "Failed to initialize, status 0x%x. Bailing out.", Status); goto Cleanup; } while (TRUE) { // // If a WM_QUIT has been received and all windows // are gone, get out. // if (fQuit && ThreadInfo.WindowCount == 0) { break; } // // Make sure we don't hold any locks while we're idle. // UserAssert(NtCurrentTeb()->CountOfOwnedCriticalSections == 0); GetMessage(&msg, NULL, 0, 0); // // Trap messages posted to the thread. // if (msg.message == CM_CREATE_CONSOLE_WINDOW) { ProcessCreateConsoleWindow(&msg); continue; } else if (msg.message == WM_QUIT) { // // The message was posted from ExitWindows. This // means that it's OK to terminate the thread. // fQuit = TRUE; // // Only exit the loop if there are no windows, // if (ThreadInfo.WindowCount == 0) { break; } RIPMSG0(RIP_WARNING, "WM_QUIT received by console with windows"); continue; } else if (CONSOLE_IS_IME_ENABLED()) { if (msg.message == CM_CONSOLE_INPUT_THREAD_MSG) { if (UnqueueThreadMessage(ThreadInfo.ThreadId, &msg.message, &msg.wParam, &msg.lParam)) { RIPMSG3(RIP_VERBOSE, "InputThread: Unqueue: msg=%04x (%08x, %08x)", msg.message, msg.wParam, msg.lParam); switch (msg.message) { case CM_CONIME_CREATE: ProcessCreateConsoleIME(&msg, ThreadInfo.ThreadId); continue; case CM_WAIT_CONIME_PROCESS: WaitConsoleIMEStuff((HDESK)msg.wParam, (HANDLE)msg.lParam); continue; case CM_SET_CONSOLEIME_WINDOW: ThreadInfo.hWndConsoleIME = (HWND)msg.wParam; continue; default: RIPMSG1(RIP_WARNING, "ConsoleInputThread: invalid thread message(%04x) !!", msg.message); break; } } else { RIPMSGF1(RIP_WARNING, "Bogus thread message posted (msg = 0x%x).", msg.message); continue; } } } if (!TranslateMessageEx(&msg, TM_POSTCHARBREAKS)) { DispatchMessage(&msg); } else { // do this so that alt-tab works while journalling if (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_TAB && (msg.lParam & 0x20000000) ) { // alt is really down DispatchMessage(&msg); } else { StoreKeyInfo(&msg); } } } // // Cleanup the input thread messages. // CleanupInputThreadMessages(ThreadInfo.ThreadId); UserAssert(Status == STATUS_SUCCESS); Cleanup: // // Free all resources used by this thread // if (hhook != NULL) { UnhookWindowsHookEx(hhook); } ConsoleDesktopInfo.dwThreadId = 0; NtUserConsoleControl(ConsoleDesktopConsoleThread, &ConsoleDesktopInfo, sizeof(ConsoleDesktopInfo)); // // Close the desktop handle. CSR is special cased to close // the handle even if the thread has windows. The desktop // remains assigned to the thread. // UserVerify(CloseDesktop(ThreadInfo.Desktop)); // // Restore thread handle so that CSR won't get confused. // if (hThread != NULL) { pcsrt->ThreadHandle = hThread; } if (pcsrt != NULL) { CsrDereferenceThread(pcsrt); } UserExitWorkerThread(Status); return Status; } ULONG GetControlKeyState( LPARAM lParam ) { ULONG ControlKeyState = 0; if (GetKeyState(VK_LMENU) & KEY_PRESSED) { ControlKeyState |= LEFT_ALT_PRESSED; } if (GetKeyState(VK_RMENU) & KEY_PRESSED) { ControlKeyState |= RIGHT_ALT_PRESSED; } if (GetKeyState(VK_LCONTROL) & KEY_PRESSED) { ControlKeyState |= LEFT_CTRL_PRESSED; } if (GetKeyState(VK_RCONTROL) & KEY_PRESSED) { ControlKeyState |= RIGHT_CTRL_PRESSED; } if (GetKeyState(VK_SHIFT) & KEY_PRESSED) { ControlKeyState |= SHIFT_PRESSED; } if (GetKeyState(VK_NUMLOCK) & KEY_TOGGLED) { ControlKeyState |= NUMLOCK_ON; } if (GetKeyState(VK_OEM_SCROLL) & KEY_TOGGLED) { ControlKeyState |= SCROLLLOCK_ON; } if (GetKeyState(VK_CAPITAL) & KEY_TOGGLED) { ControlKeyState |= CAPSLOCK_ON; } if (lParam & KEY_ENHANCED) { ControlKeyState |= ENHANCED_KEY; } ControlKeyState |= (lParam & ALTNUMPAD_BIT); return ControlKeyState; } ULONG ConvertMouseButtonState( IN ULONG Flag, IN ULONG State ) { if (State & MK_LBUTTON) { Flag |= FROM_LEFT_1ST_BUTTON_PRESSED; } if (State & MK_MBUTTON) { Flag |= FROM_LEFT_2ND_BUTTON_PRESSED; } if (State & MK_RBUTTON) { Flag |= RIGHTMOST_BUTTON_PRESSED; } return Flag; } VOID TerminateRead( IN PCONSOLE_INFORMATION Console, IN PINPUT_INFORMATION InputInfo, IN DWORD Flag ) /*++ Routine Description: This routine wakes up any readers waiting for data when a ctrl-c or ctrl-break is input. Arguments: InputInfo - pointer to input buffer Flag - flag indicating whether ctrl-break or ctrl-c was input --*/ { BOOLEAN WaitSatisfied; WaitSatisfied = CsrNotifyWait(&InputInfo->ReadWaitQueue, TRUE, NULL, IntToPtr(Flag) ); if (WaitSatisfied) { // #334370 under stress, WaitQueue may already hold the satisfied waits UserAssert((Console->WaitQueue == NULL) || (Console->WaitQueue == &InputInfo->ReadWaitQueue)); Console->WaitQueue = &InputInfo->ReadWaitQueue; } } BOOL HandleSysKeyEvent( IN PCONSOLE_INFORMATION Console, IN HWND hWnd, IN UINT Message, IN WPARAM wParam, IN LPARAM lParam, IN PBOOL pbUnlockConsole ) /* returns TRUE if DefWindowProc should be called. */ { WORD VirtualKeyCode; BOOL bCtrlDown; #ifndef i386 UNREFERENCED_PARAMETER(pbUnlockConsole); #endif #if defined (FE_IME) // Sep.16.1995 Support Console IME by v-HirShi(Hirotoshi Shimizu) if (Message == WM_SYSCHAR || Message == WM_SYSDEADCHAR || Message == WM_SYSCHAR+CONIME_KEYDATA || Message == WM_SYSDEADCHAR+CONIME_KEYDATA) #else if (Message == WM_SYSCHAR || Message == WM_SYSDEADCHAR) #endif { VirtualKeyCode = (WORD)MapVirtualKey(LOBYTE(HIWORD(lParam)), 1); } else { VirtualKeyCode = LOWORD(wParam); } // // check for ctrl-esc // bCtrlDown = GetKeyState(VK_CONTROL) & KEY_PRESSED; if (VirtualKeyCode == VK_ESCAPE && bCtrlDown && !(GetKeyState(VK_MENU) & KEY_PRESSED) && !(GetKeyState(VK_SHIFT) & KEY_PRESSED) && !(Console->ReserveKeys & CONSOLE_CTRLESC) ) { return TRUE; // call DefWindowProc } if ((lParam & 0x20000000) == 0) { // we're iconic // // Check for ENTER while iconic (restore accelerator). // if (VirtualKeyCode == VK_RETURN && !(Console->FullScreenFlags & CONSOLE_FULLSCREEN_HARDWARE)) { return TRUE; // call DefWindowProc } else { HandleKeyEvent(Console, hWnd, Message, wParam, lParam ); return FALSE; } } if (VirtualKeyCode == VK_RETURN && !bCtrlDown && !(Console->ReserveKeys & CONSOLE_ALTENTER)) { #ifdef i386 if (!(Message & KEY_UP_TRANSITION)) { if (FullScreenInitialized && !GetSystemMetrics(SM_REMOTESESSION)) { if (Console->FullScreenFlags == 0) { ConvertToFullScreen(Console); Console->FullScreenFlags = CONSOLE_FULLSCREEN; ChangeDispSettings(Console, Console->hWnd, CDS_FULLSCREEN); } else { ConvertToWindowed(Console); Console->FullScreenFlags &= ~CONSOLE_FULLSCREEN; ChangeDispSettings(Console, Console->hWnd,0); ShowWindow(Console->hWnd, SW_RESTORE); } } else { WCHAR ItemString[70]; HWND hwnd = Console->hWnd; LPWSTR lpTitle; /* * We must unlock the console around the MessageBox call, * since this can block indefinitely long (otherwise, any * thread that tries to access this console will get hung * trying to acquire its lock). */ lpTitle = ConsoleHeapAlloc(TMP_TAG, Console->TitleLength + sizeof(WCHAR)); if (lpTitle) { RtlCopyMemory(lpTitle, Console->Title, Console->TitleLength); lpTitle[Console->TitleLength / sizeof(WCHAR)] = 0; UnlockConsole(Console); *pbUnlockConsole = FALSE; LoadString(ghInstance, msgNoFullScreen, ItemString, ARRAY_SIZE(ItemString)); ++DialogBoxCount; MessageBox(hWnd, ItemString, lpTitle, MB_SYSTEMMODAL | MB_OK); --DialogBoxCount; ConsoleHeapFree(lpTitle); } } } #endif return FALSE; } // // make sure alt-space gets translated so that the system // menu is displayed. // if (!(GetKeyState(VK_CONTROL) & KEY_PRESSED)) { if (VirtualKeyCode == VK_SPACE && !(Console->ReserveKeys & CONSOLE_ALTSPACE)) { return TRUE; // call DefWindowProc } if (VirtualKeyCode == VK_ESCAPE && !(Console->ReserveKeys & CONSOLE_ALTESC)) { return TRUE; // call DefWindowProc } if (VirtualKeyCode == VK_TAB && !(Console->ReserveKeys & CONSOLE_ALTTAB)) { return TRUE; // call DefWindowProc } } HandleKeyEvent(Console, hWnd, Message, wParam, lParam); return FALSE; } VOID HandleKeyEvent( IN PCONSOLE_INFORMATION Console, IN HWND hWnd, IN UINT Message, IN WPARAM wParam, IN LPARAM lParam ) { INPUT_RECORD InputEvent; BOOLEAN ContinueProcessing; ULONG EventsWritten; WORD VirtualKeyCode; ULONG ControlKeyState; BOOL bKeyDown; BOOL bGenerateBreak = FALSE; #ifdef FE_SB BOOL KeyMessageFromConsoleIME; #endif #ifdef FE_SB // v-HirShi Sep.21.1995 For Console IME if ((WM_KEYFIRST+CONIME_KEYDATA) <= Message && Message <= (WM_KEYLAST+CONIME_KEYDATA)) { Message -= CONIME_KEYDATA ; KeyMessageFromConsoleIME = TRUE; } else { KeyMessageFromConsoleIME = FALSE; } #endif /* * BOGUS for WM_CHAR/WM_DEADCHAR, in which LOWORD(lParam) is a character */ VirtualKeyCode = LOWORD(wParam); ControlKeyState = GetControlKeyState(lParam); bKeyDown = !(lParam & KEY_TRANSITION_UP); // // Make sure we retrieve the key info first, or we could chew up // unneeded space in the key info table if we bail out early. // InputEvent.Event.KeyEvent.wVirtualKeyCode = VirtualKeyCode; InputEvent.Event.KeyEvent.wVirtualScanCode = (BYTE)(HIWORD(lParam)); if (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR) { RetrieveKeyInfo(hWnd, &InputEvent.Event.KeyEvent.wVirtualKeyCode, &InputEvent.Event.KeyEvent.wVirtualScanCode, !(Console->InputBuffer.ImeMode.Open ^ KeyMessageFromConsoleIME)); VirtualKeyCode = InputEvent.Event.KeyEvent.wVirtualKeyCode; } // // If this is a key up message, should we ignore it? We do this // so that if a process reads a line from the input buffer, the // key up event won't get put in the buffer after the read // completes. // if (Console->Flags & CONSOLE_IGNORE_NEXT_KEYUP) { Console->Flags &= ~CONSOLE_IGNORE_NEXT_KEYUP; if (!bKeyDown) { return; } } #ifdef FE_SB // v-HirShi Sep.21.1995 For Console IME if (KeyMessageFromConsoleIME) { goto FromConsoleIME ; } #endif if (Console->Flags & CONSOLE_SELECTING) { if (!bKeyDown) { return; } // // if escape or ctrl-c, cancel selection // if (!(Console->SelectionFlags & CONSOLE_MOUSE_DOWN) ) { if (VirtualKeyCode == VK_ESCAPE || (VirtualKeyCode == 'C' && (GetKeyState(VK_CONTROL) & KEY_PRESSED) )) { ClearSelection(Console); return; } else if (VirtualKeyCode == VK_RETURN) { // if return, copy selection DoCopy(Console); return; } else if (gfEnableColorSelection && ('0' <= VirtualKeyCode) && ('9' >= VirtualKeyCode) && (Console->CurrentScreenBuffer->Flags & CONSOLE_TEXTMODE_BUFFER)) { BOOLEAN AltPressed, ShiftPressed, CtrlPressed = FALSE; PSMALL_RECT Selection = &Console->SelectionRect; // // It's a numeric key, a text mode buffer and the color selection regkey is set, // then check to see if the user want's to color the selection or search and // highlight the selection. // AltPressed = (ControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0; ShiftPressed = (ControlKeyState & SHIFT_PRESSED) != 0; // // Shift implies a find-and-color operation. We only support finding a string, not // a block. So if the selected area is > 1 line in height, just ignore the shift // and color the selection. Also ignore if there is no current selection. // if ((ShiftPressed) && ( !(Console->SelectionFlags & CONSOLE_SELECTION_NOT_EMPTY) || (Selection->Top != Selection->Bottom))) { ShiftPressed = FALSE; } // // If CTRL + ALT together, then we interpret as ALT (eg on French // keyboards AltGr == RALT+LCTRL, but we want it to behave as ALT). // if (!AltPressed) { CtrlPressed = (ControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) != 0; } // // Clip the selection to within the console buffer // if (Selection->Left < 0) { Selection->Left = 0; } if (Selection->Top < 0) { Selection->Top = 0; } if (Selection->Right >= Console->CurrentScreenBuffer->ScreenBufferSize.X) { Selection->Right = (SHORT)(Console->CurrentScreenBuffer->ScreenBufferSize.X-1); } if (Selection->Bottom >= Console->CurrentScreenBuffer->ScreenBufferSize.Y) { Selection->Bottom = (SHORT)(Console->CurrentScreenBuffer->ScreenBufferSize.Y-1); } // // If ALT or CTRL are pressed, then color the selected area. // ALT+n => fg, CTRL+n => bg // if (AltPressed || CtrlPressed) { ULONG Attr = VirtualKeyCode - '0' + 6; if (CtrlPressed) { // // Setting background color. Set fg color to black. // Attr <<= 4; } else { // // Set foreground color. Maintain the current console bg color // Attr |= Console->CurrentScreenBuffer->Attributes & 0xf0; } // // If shift was pressed as well, then this is actually a find-and-color // request. Otherwise just color the selection. // if (ShiftPressed) { WCHAR SearchString[SEARCH_STRING_LENGTH + 1]; ULONG Length, RowIndex; PROW Row; PSCREEN_INFORMATION ScreenInfo = Console->CurrentScreenBuffer; Length = Selection->Right - Selection->Left + 1; if (Length > SEARCH_STRING_LENGTH) { Length = SEARCH_STRING_LENGTH; } // // Pull the selection out of the buffer to pass to the search function. // Clamp to max search string length. We just copy the bytes out of // the row buffer. // RowIndex = (Selection->Top + ScreenInfo->BufferInfo.TextInfo.FirstRow) % ScreenInfo->ScreenBufferSize.Y; Row = &ScreenInfo->BufferInfo.TextInfo.Rows[ RowIndex]; RtlCopyMemory( SearchString, &Row->CharRow.Chars[ Selection->Left], Length * sizeof( WCHAR)); SearchString[ Length] = L'\0'; // // Clear the selection, and call the search / mark function. // ClearSelection(Console); SearchForString( ScreenInfo, SearchString, (USHORT)Length, TRUE, FALSE, TRUE, Attr, NULL); } else { ColorSelection( Console, Selection, Attr); ClearSelection(Console); } return; } } } if (!(Console->SelectionFlags & CONSOLE_MOUSE_SELECTION)) { if ((Console->CurrentScreenBuffer->Flags & CONSOLE_TEXTMODE_BUFFER) && (VirtualKeyCode == VK_RIGHT || VirtualKeyCode == VK_LEFT || VirtualKeyCode == VK_UP || VirtualKeyCode == VK_DOWN || VirtualKeyCode == VK_NEXT || VirtualKeyCode == VK_PRIOR || VirtualKeyCode == VK_END || VirtualKeyCode == VK_HOME ) ) { PSCREEN_INFORMATION ScreenInfo; #ifdef FE_SB SHORT RowIndex; PROW Row; BYTE KAttrs; SHORT NextRightX; SHORT NextLeftX; #endif ScreenInfo = Console->CurrentScreenBuffer; // // see if shift is down. if so, we're extending // the selection. otherwise, we're resetting the // anchor // ConsoleHideCursor(ScreenInfo); #ifdef FE_SB RowIndex = (ScreenInfo->BufferInfo.TextInfo.FirstRow+ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y) % ScreenInfo->ScreenBufferSize.Y; Row = &ScreenInfo->BufferInfo.TextInfo.Rows[RowIndex]; if (CONSOLE_IS_DBCS_OUTPUTCP(Console)) { KAttrs = Row->CharRow.KAttrs[ScreenInfo->BufferInfo.TextInfo.CursorPosition.X]; if (KAttrs & ATTR_LEADING_BYTE) NextRightX = 2; else NextRightX = 1; } else { NextRightX = 1; } if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.X > 0) { if (CONSOLE_IS_DBCS_OUTPUTCP(Console)) { KAttrs = Row->CharRow.KAttrs[ScreenInfo->BufferInfo.TextInfo.CursorPosition.X-1]; if (KAttrs & ATTR_TRAILING_BYTE) NextLeftX = 2; else if (KAttrs & ATTR_LEADING_BYTE) { if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.X-1 > 0) { KAttrs = Row->CharRow.KAttrs[ScreenInfo->BufferInfo.TextInfo.CursorPosition.X-2]; if (KAttrs & ATTR_TRAILING_BYTE) NextLeftX = 3; else NextLeftX = 2; } else NextLeftX = 1; } else NextLeftX = 1; } else NextLeftX = 1; } switch (VirtualKeyCode) { case VK_RIGHT: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.X+NextRightX < ScreenInfo->ScreenBufferSize.X) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.X+=NextRightX; } break; case VK_LEFT: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.X > 0) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.X-=NextLeftX; } break; case VK_UP: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y > 0) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y-=1; } break; case VK_DOWN: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y+1 < ScreenInfo->ScreenBufferSize.Y) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y+=1; } break; case VK_NEXT: ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y += CONSOLE_WINDOW_SIZE_Y(ScreenInfo)-1; if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y >= ScreenInfo->ScreenBufferSize.Y) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = ScreenInfo->ScreenBufferSize.Y-1; } break; case VK_PRIOR: ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y -= CONSOLE_WINDOW_SIZE_Y(ScreenInfo)-1; if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y < 0) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = 0; } break; case VK_END: ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = ScreenInfo->ScreenBufferSize.Y-CONSOLE_WINDOW_SIZE_Y(ScreenInfo); break; case VK_HOME: ScreenInfo->BufferInfo.TextInfo.CursorPosition.X = 0; ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = 0; break; default: UserAssert(FALSE); } #else // FE_SB switch (VirtualKeyCode) { case VK_RIGHT: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.X+1 < ScreenInfo->ScreenBufferSize.X) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.X+=1; } break; case VK_LEFT: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.X > 0) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.X-=1; } break; case VK_UP: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y > 0) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y-=1; } break; case VK_DOWN: if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y+1 < ScreenInfo->ScreenBufferSize.Y) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y+=1; } break; case VK_NEXT: ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y += CONSOLE_WINDOW_SIZE_Y(ScreenInfo)-1; if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y >= ScreenInfo->ScreenBufferSize.Y) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = ScreenInfo->ScreenBufferSize.Y-1; } break; case VK_PRIOR: ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y -= CONSOLE_WINDOW_SIZE_Y(ScreenInfo)-1; if (ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y < 0) { ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = 0; } break; case VK_END: ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = ScreenInfo->ScreenBufferSize.Y-CONSOLE_WINDOW_SIZE_Y(ScreenInfo); break; case VK_HOME: ScreenInfo->BufferInfo.TextInfo.CursorPosition.X = 0; ScreenInfo->BufferInfo.TextInfo.CursorPosition.Y = 0; break; default: UserAssert(FALSE); } #endif // FE_SB ConsoleShowCursor(ScreenInfo); if (GetKeyState(VK_SHIFT) & KEY_PRESSED) { { ExtendSelection(Console,ScreenInfo->BufferInfo.TextInfo.CursorPosition); } } else { if (Console->SelectionFlags & CONSOLE_SELECTION_NOT_EMPTY) { MyInvert(Console,&Console->SelectionRect); Console->SelectionFlags &= ~CONSOLE_SELECTION_NOT_EMPTY; ConsoleShowCursor(ScreenInfo); } ScreenInfo->BufferInfo.TextInfo.CursorMoved = TRUE; Console->SelectionAnchor = ScreenInfo->BufferInfo.TextInfo.CursorPosition; MakeCursorVisible(ScreenInfo,Console->SelectionAnchor); Console->SelectionRect.Left = Console->SelectionRect.Right = Console->SelectionAnchor.X; Console->SelectionRect.Top = Console->SelectionRect.Bottom = Console->SelectionAnchor.Y; } return; } } else if (!(Console->SelectionFlags & CONSOLE_MOUSE_DOWN)) { // // if in mouse selection mode and user hits a key, cancel selection // if (!IsSystemKey(VirtualKeyCode)) { ClearSelection(Console); } } } else if (Console->Flags & CONSOLE_SCROLLING) { if (!bKeyDown) { return; } // // if escape, enter or ctrl-c, cancel scroll // if (VirtualKeyCode == VK_ESCAPE || VirtualKeyCode == VK_RETURN || (VirtualKeyCode == 'C' && (GetKeyState(VK_CONTROL) & KEY_PRESSED) )) { ClearScroll(Console); } else { WORD ScrollCommand; BOOL Horizontal=FALSE; switch (VirtualKeyCode) { case VK_UP: ScrollCommand = SB_LINEUP; break; case VK_DOWN: ScrollCommand = SB_LINEDOWN; break; case VK_LEFT: ScrollCommand = SB_LINEUP; Horizontal=TRUE; break; case VK_RIGHT: ScrollCommand = SB_LINEDOWN; Horizontal=TRUE; break; case VK_NEXT: ScrollCommand = SB_PAGEDOWN; break; case VK_PRIOR: ScrollCommand = SB_PAGEUP; break; case VK_END: ScrollCommand = SB_PAGEDOWN; Horizontal=TRUE; break; case VK_HOME: ScrollCommand = SB_PAGEUP; Horizontal=TRUE; break; case VK_SHIFT: case VK_CONTROL: case VK_MENU: return; default: Beep(800, 200); return; } if (Horizontal) { HorizontalScroll(Console->CurrentScreenBuffer, ScrollCommand, 0); } else { VerticalScroll(Console, Console->CurrentScreenBuffer,ScrollCommand,0); } } return; } // // if the user is inputting chars at an inappropriate time, beep. // if ((Console->Flags & (CONSOLE_SELECTING | CONSOLE_SCROLLING | CONSOLE_SCROLLBAR_TRACKING)) && bKeyDown && !IsSystemKey(VirtualKeyCode)) { Beep(800, 200); return; } // // if in fullscreen mode, process PrintScreen // #ifdef LATER // // Changed this code to get commas to work (build 485). // // Therese, the problem is that WM_CHAR/WM_SYSCHAR messages come through // here - in this case, LOWORD(wParam) is a character value and not a virtual // key. It happens that VK_SNAPSHOT == 0x2c, and the character value for a // comma is also == 0x2c, so execution enters this conditional when a comma // is hit. Commas aren't coming out because of the newly entered return // statement. // // HandleKeyEvent() is making many virtual key comparisons - need to make // sure that for each one, there is either no corresponding character value, // or that you check before you compare so that you are comparing two values // that have the same data type. // // I added the message comparison so that we know we're checking virtual // keys against virtual keys and not characters. // // - scottlu // #endif if (Message != WM_CHAR && Message != WM_SYSCHAR && VirtualKeyCode == VK_SNAPSHOT && !(Console->ReserveKeys & (CONSOLE_ALTPRTSC | CONSOLE_PRTSC )) ) { if (Console->FullScreenFlags & CONSOLE_FULLSCREEN_HARDWARE) { Console->SelectionFlags |= CONSOLE_SELECTION_NOT_EMPTY; Console->SelectionRect = Console->CurrentScreenBuffer->Window; StoreSelection(Console); Console->SelectionFlags &= ~CONSOLE_SELECTION_NOT_EMPTY; } return; } // // IME stuff // if (!(Console->Flags & CONSOLE_VDM_REGISTERED)) { LPARAM lParamForHotKey ; DWORD HotkeyID ; lParamForHotKey = lParam ; HotkeyID = NtUserCheckImeHotKey( (VirtualKeyCode & 0x00ff),lParamForHotKey) ; // // If it's direct KL switching hokey, handle it here // regardless the system is IME enabled or not. // if (HotkeyID >= IME_HOTKEY_DSWITCH_FIRST && HotkeyID <= IME_HOTKEY_DSWITCH_LAST) { UINT uModifier, uVkey; HKL hkl; RIPMSG1(RIP_VERBOSE, "HandleKeyEvent: handling IME HOTKEY id=%x", HotkeyID); if (NtUserGetImeHotKey(HotkeyID, &uModifier, &uVkey, &hkl) && hkl != NULL) { BYTE bCharSetSys = CodePageToCharSet(GetACP()); WPARAM wpSysChar = 0; CHARSETINFO cs; if (TranslateCharsetInfo((LPDWORD)LOWORD(hkl), &cs, TCI_SRCLOCALE)) { if (bCharSetSys == cs.ciCharset) { wpSysChar = INPUTLANGCHANGE_SYSCHARSET; } } PostMessage(hWnd, WM_INPUTLANGCHANGEREQUEST, wpSysChar, (LPARAM)hkl); } return; } if (!(Console->InputBuffer.ImeMode.Disable) && CONSOLE_IS_IME_ENABLED()) { if (HotkeyID != IME_INVALID_HOTKEY) { switch(HotkeyID) { case IME_JHOTKEY_CLOSE_OPEN: { BOOL fOpen = Console->InputBuffer.ImeMode.Open; if (!bKeyDown) break ; Console->InputBuffer.ImeMode.Open = !fOpen ; if (!NT_SUCCESS(ConsoleImeMessagePump(Console, CONIME_HOTKEY, (WPARAM)Console->ConsoleHandle, HotkeyID))) { break; } // Update in the system conversion mode buffer. GetImeKeyState(Console, NULL); break ; } case IME_CHOTKEY_IME_NONIME_TOGGLE: case IME_THOTKEY_IME_NONIME_TOGGLE: case IME_CHOTKEY_SHAPE_TOGGLE: case IME_THOTKEY_SHAPE_TOGGLE: case IME_CHOTKEY_SYMBOL_TOGGLE: case IME_THOTKEY_SYMBOL_TOGGLE: case IME_KHOTKEY_SHAPE_TOGGLE: case IME_KHOTKEY_HANJACONVERT: case IME_KHOTKEY_ENGLISH: case IME_ITHOTKEY_RESEND_RESULTSTR: case IME_ITHOTKEY_PREVIOUS_COMPOSITION: case IME_ITHOTKEY_UISTYLE_TOGGLE: default: { if (!NT_SUCCESS(ConsoleImeMessagePump(Console, CONIME_HOTKEY, (WPARAM)Console->ConsoleHandle, HotkeyID))) { break; } // Update in the system conversion mode buffer. GetImeKeyState(Console, NULL); break ; } } return ; } if ( CTRL_BUT_NOT_ALT(ControlKeyState) && (bKeyDown) ) { if (VirtualKeyCode == 'C' && Console->InputBuffer.InputMode & ENABLE_PROCESSED_INPUT) { goto FromConsoleIME ; } else if (VirtualKeyCode == VK_CANCEL) { goto FromConsoleIME ; } else if (VirtualKeyCode == 'S'){ goto FromConsoleIME ; } } else if (VirtualKeyCode == VK_PAUSE ){ goto FromConsoleIME ; } else if ( ((VirtualKeyCode == VK_SHIFT) || (VirtualKeyCode == VK_CONTROL) || (VirtualKeyCode == VK_CAPITAL) || (VirtualKeyCode == VK_KANA) || // VK_KANA == VK_HANGUL (VirtualKeyCode == VK_JUNJA) || (VirtualKeyCode == VK_HANJA) || (VirtualKeyCode == VK_NUMLOCK) || (VirtualKeyCode == VK_SCROLL) ) && !(Console->InputBuffer.ImeMode.Unavailable) && !(Console->InputBuffer.ImeMode.Open) ) { if (!NT_SUCCESS(ConsoleImeMessagePump(Console, Message+CONIME_KEYDATA, (WPARAM)LOWORD(wParam)<<16|LOWORD(VirtualKeyCode), lParam ))) { return; } goto FromConsoleIME ; } if (!Console->InputBuffer.ImeMode.Unavailable && Console->InputBuffer.ImeMode.Open) { if (! (HIWORD(lParam) & KF_REPEAT)) { if (PRIMARYLANGID(LOWORD(Console->hklActive)) == LANG_JAPANESE && (BYTE)wParam == VK_KANA) { if (!NT_SUCCESS(ConsoleImeMessagePump(Console, CONIME_NOTIFY_VK_KANA, 0, 0 ))) { return; } } } ConsoleImeMessagePump(Console, Message+CONIME_KEYDATA, LOWORD(wParam)<<16|LOWORD(VirtualKeyCode), lParam ); return ; } } } FromConsoleIME: // // ignore key strokes that will generate CHAR messages. this is only // necessary while a dialog box is up. // if (DialogBoxCount > 0) { if (Message != WM_CHAR && Message != WM_SYSCHAR && Message != WM_DEADCHAR && Message != WM_SYSDEADCHAR) { WCHAR awch[MAX_CHARS_FROM_1_KEYSTROKE]; int cwch; BYTE KeyState[256]; GetKeyboardState(KeyState); cwch = ToUnicodeEx((UINT)wParam, HIWORD(lParam), KeyState, awch, ARRAY_SIZE(awch), TM_POSTCHARBREAKS, NULL); if (cwch != 0) { return; } } else { // remember to generate break if (Message == WM_CHAR) { bGenerateBreak = TRUE; } } } #ifdef FE_IME // ignore key stroke while IME property is up. if (Console->InputBuffer.hWndConsoleIME) return; #endif InputEvent.EventType = KEY_EVENT; InputEvent.Event.KeyEvent.bKeyDown = bKeyDown; InputEvent.Event.KeyEvent.wRepeatCount = LOWORD(lParam); if (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR) { // If this is a fake character, zero the scancode. if (lParam & 0x02000000) { InputEvent.Event.KeyEvent.wVirtualScanCode = 0; } InputEvent.Event.KeyEvent.dwControlKeyState = GetControlKeyState(lParam); if (Message == WM_CHAR || Message == WM_SYSCHAR) { InputEvent.Event.KeyEvent.uChar.UnicodeChar = (WCHAR)wParam; } else { InputEvent.Event.KeyEvent.uChar.UnicodeChar = (WCHAR)0; } } else { // if alt-gr, ignore if (lParam & 0x02000000) { return; } InputEvent.Event.KeyEvent.dwControlKeyState = ControlKeyState; InputEvent.Event.KeyEvent.uChar.UnicodeChar = 0; } #ifdef FE_IME if (CONSOLE_IS_IME_ENABLED()) { // MSKK August.22.1993 KazuM DWORD dwConversion; if (!NT_SUCCESS(GetImeKeyState(Console, &dwConversion))) { return; } InputEvent.Event.KeyEvent.dwControlKeyState |= ImmConversionToConsole(dwConversion); } #endif ContinueProcessing=TRUE; if (CTRL_BUT_NOT_ALT(InputEvent.Event.KeyEvent.dwControlKeyState) && InputEvent.Event.KeyEvent.bKeyDown) { // // check for ctrl-c, if in line input mode. // if (InputEvent.Event.KeyEvent.wVirtualKeyCode == 'C' && Console->InputBuffer.InputMode & ENABLE_PROCESSED_INPUT) { HandleCtrlEvent(Console,CTRL_C_EVENT); if (!Console->PopupCount) TerminateRead(Console,&Console->InputBuffer,CONSOLE_CTRL_C_SEEN); if (!(Console->Flags & CONSOLE_SUSPENDED)) { ContinueProcessing=FALSE; } } // // check for ctrl-break. // else if (InputEvent.Event.KeyEvent.wVirtualKeyCode == VK_CANCEL) { FlushInputBuffer(&Console->InputBuffer); HandleCtrlEvent(Console,CTRL_BREAK_EVENT); if (!Console->PopupCount) TerminateRead(Console,&Console->InputBuffer,CONSOLE_CTRL_BREAK_SEEN); if (!(Console->Flags & CONSOLE_SUSPENDED)) { ContinueProcessing=FALSE; } } // // don't write ctrl-esc to the input buffer // else if (InputEvent.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE && !(Console->ReserveKeys & CONSOLE_CTRLESC)) { ContinueProcessing=FALSE; } } else if (InputEvent.Event.KeyEvent.dwControlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED) && InputEvent.Event.KeyEvent.bKeyDown && InputEvent.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE && !(Console->ReserveKeys & CONSOLE_ALTESC)) { ContinueProcessing=FALSE; } if (ContinueProcessing) { EventsWritten = WriteInputBuffer( Console, &Console->InputBuffer, &InputEvent, 1 ); if (EventsWritten && bGenerateBreak) { InputEvent.Event.KeyEvent.bKeyDown = FALSE; WriteInputBuffer( Console, &Console->InputBuffer, &InputEvent, 1 ); } } } /* * Returns TRUE if DefWindowProc should be called. */ BOOL HandleMouseEvent( IN PCONSOLE_INFORMATION Console, IN PSCREEN_INFORMATION ScreenInfo, IN UINT Message, IN WPARAM wParam, IN LPARAM lParam) { ULONG ButtonFlags,EventFlags; INPUT_RECORD InputEvent; ULONG EventsWritten; COORD MousePosition; SHORT RowIndex; PROW Row; if (!(Console->Flags & CONSOLE_HAS_FOCUS) && !(Console->FullScreenFlags & CONSOLE_FULLSCREEN_HARDWARE) && !(Console->SelectionFlags & CONSOLE_MOUSE_DOWN)) { return TRUE; } if (Console->Flags & CONSOLE_IGNORE_NEXT_MOUSE_INPUT) { // only reset on up transition if (Message != WM_LBUTTONDOWN && Message != WM_MBUTTONDOWN && Message != WM_RBUTTONDOWN) { Console->Flags &= ~CONSOLE_IGNORE_NEXT_MOUSE_INPUT; return FALSE; } return TRUE; } // // translate mouse position into characters, if necessary. // MousePosition.X = LOWORD(lParam); MousePosition.Y = HIWORD(lParam); if (ScreenInfo->Flags & CONSOLE_TEXTMODE_BUFFER) { MousePosition.X /= SCR_FONTSIZE(ScreenInfo).X; MousePosition.Y /= SCR_FONTSIZE(ScreenInfo).Y; } MousePosition.X += ScreenInfo->Window.Left; MousePosition.Y += ScreenInfo->Window.Top; // // make sure mouse position is clipped to screen buffer // if (MousePosition.X < 0) { MousePosition.X = 0; } else if (MousePosition.X >= ScreenInfo->ScreenBufferSize.X) { MousePosition.X = ScreenInfo->ScreenBufferSize.X - 1; } if (MousePosition.Y < 0) { MousePosition.Y = 0; } else if (MousePosition.Y >= ScreenInfo->ScreenBufferSize.Y) { MousePosition.Y = ScreenInfo->ScreenBufferSize.Y - 1; } if (Console->Flags & CONSOLE_SELECTING || ((Console->Flags & CONSOLE_QUICK_EDIT_MODE) && (Console->FullScreenFlags == 0))) { if (Message == WM_LBUTTONDOWN) { // // make sure message matches button state // if (!(GetKeyState(VK_LBUTTON) & KEY_PRESSED)) { return FALSE; } if (Console->Flags & CONSOLE_QUICK_EDIT_MODE && !(Console->Flags & CONSOLE_SELECTING)) { Console->Flags |= CONSOLE_SELECTING; Console->SelectionFlags = CONSOLE_MOUSE_SELECTION | CONSOLE_MOUSE_DOWN | CONSOLE_SELECTION_NOT_EMPTY; // // invert selection // InitializeMouseSelection(Console, MousePosition); MyInvert(Console,&Console->SelectionRect); SetWinText(Console,msgSelectMode,TRUE); SetCapture(Console->hWnd); } else { // // We now capture the mouse to our Window. We do this so that the // user can "scroll" the selection endpoint to an off screen // position by moving the mouse off the client area. // if (Console->SelectionFlags & CONSOLE_MOUSE_SELECTION) { // // Check for SHIFT-Mouse Down "continue previous selection" // command. // if (GetKeyState(VK_SHIFT) & KEY_PRESSED) { Console->SelectionFlags |= CONSOLE_MOUSE_DOWN; SetCapture(Console->hWnd); ExtendSelection(Console, MousePosition); } else { // // Invert old selection, reset anchor, and invert // new selection. // MyInvert(Console,&Console->SelectionRect); Console->SelectionFlags |= CONSOLE_MOUSE_DOWN; SetCapture(Console->hWnd); InitializeMouseSelection(Console, MousePosition); MyInvert(Console,&Console->SelectionRect); } } else { ConvertToMouseSelect(Console, MousePosition); } } } else if (Message == WM_LBUTTONUP) { if (Console->SelectionFlags & CONSOLE_MOUSE_SELECTION) { Console->SelectionFlags &= ~CONSOLE_MOUSE_DOWN; ReleaseCapture(); } } else if (Message == WM_LBUTTONDBLCLK) { if ((MousePosition.X == Console->SelectionAnchor.X) && (MousePosition.Y == Console->SelectionAnchor.Y)) { RowIndex = (ScreenInfo->BufferInfo.TextInfo.FirstRow+MousePosition.Y) % ScreenInfo->ScreenBufferSize.Y; Row = &ScreenInfo->BufferInfo.TextInfo.Rows[RowIndex]; while (Console->SelectionAnchor.X > 0) { if (IS_WORD_DELIM(Row->CharRow.Chars[Console->SelectionAnchor.X - 1])) { break; } Console->SelectionAnchor.X--; } while (MousePosition.X < ScreenInfo->ScreenBufferSize.X) { if (IS_WORD_DELIM(Row->CharRow.Chars[MousePosition.X])) { break; } MousePosition.X++; } if (gfTrimLeadingZeros) { // // Trim the leading zeros: 000fe12 -> fe12, except 0x and 0n. // Useful for debugging // if (MousePosition.X > Console->SelectionAnchor.X + 2 && Row->CharRow.Chars[Console->SelectionAnchor.X + 1] != L'x' && Row->CharRow.Chars[Console->SelectionAnchor.X + 1] != L'X' && Row->CharRow.Chars[Console->SelectionAnchor.X + 1] != L'n') { // Don't touch the selection begins with 0x while (Row->CharRow.Chars[Console->SelectionAnchor.X] == L'0' && Console->SelectionAnchor.X < MousePosition.X - 1) { Console->SelectionAnchor.X++; } } } ExtendSelection(Console, MousePosition); } } else if ((Message == WM_RBUTTONDOWN) || (Message == WM_RBUTTONDBLCLK)) { if (!(Console->SelectionFlags & CONSOLE_MOUSE_DOWN)) { if (Console->Flags & CONSOLE_SELECTING) { DoCopy(Console); } else if (Console->Flags & CONSOLE_QUICK_EDIT_MODE) { DoPaste(Console); } Console->Flags |= CONSOLE_IGNORE_NEXT_MOUSE_INPUT; } } else if (Message == WM_MOUSEMOVE) { if (Console->SelectionFlags & CONSOLE_MOUSE_DOWN) { ExtendSelection(Console, MousePosition); } } else if (Message == WM_MOUSEWHEEL) { return TRUE; } return FALSE; } if (!(Console->InputBuffer.InputMode & ENABLE_MOUSE_INPUT)) { ReleaseCapture(); if (Console->FullScreenFlags == 0) { return TRUE; } return FALSE; } InputEvent.Event.MouseEvent.dwControlKeyState = GetControlKeyState(0); if (Console->FullScreenFlags & CONSOLE_FULLSCREEN_HARDWARE) { if (MousePosition.X > ScreenInfo->Window.Right) { MousePosition.X = ScreenInfo->Window.Right; } if (MousePosition.Y > ScreenInfo->Window.Bottom) { MousePosition.Y = ScreenInfo->Window.Bottom; } } switch (Message) { case WM_LBUTTONDOWN: SetCapture(Console->hWnd); ButtonFlags = FROM_LEFT_1ST_BUTTON_PRESSED; EventFlags = 0; break; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: ReleaseCapture(); ButtonFlags = EventFlags = 0; break; case WM_RBUTTONDOWN: SetCapture(Console->hWnd); ButtonFlags = RIGHTMOST_BUTTON_PRESSED; EventFlags = 0; break; case WM_MBUTTONDOWN: SetCapture(Console->hWnd); ButtonFlags = FROM_LEFT_2ND_BUTTON_PRESSED; EventFlags = 0; break; case WM_MOUSEMOVE: ButtonFlags = 0; EventFlags = MOUSE_MOVED; break; case WM_LBUTTONDBLCLK: ButtonFlags = FROM_LEFT_1ST_BUTTON_PRESSED; EventFlags = DOUBLE_CLICK; break; case WM_RBUTTONDBLCLK: ButtonFlags = RIGHTMOST_BUTTON_PRESSED; EventFlags = DOUBLE_CLICK; break; case WM_MBUTTONDBLCLK: ButtonFlags = FROM_LEFT_2ND_BUTTON_PRESSED; EventFlags = DOUBLE_CLICK; break; case WM_MOUSEWHEEL: ButtonFlags = ((UINT)wParam & 0xFFFF0000); EventFlags = MOUSE_WHEELED; break; default: RIPMSG1(RIP_ERROR, "Invalid message 0x%x", Message); } InputEvent.EventType = MOUSE_EVENT; InputEvent.Event.MouseEvent.dwMousePosition = MousePosition; InputEvent.Event.MouseEvent.dwEventFlags = EventFlags; InputEvent.Event.MouseEvent.dwButtonState = ConvertMouseButtonState(ButtonFlags, (UINT)wParam); EventsWritten = WriteInputBuffer(Console, &Console->InputBuffer, &InputEvent, 1 ); if (EventsWritten != 1) { RIPMSG1(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1 (0x%x), 1 expected", EventsWritten); } #ifdef i386 if (Console->FullScreenFlags & CONSOLE_FULLSCREEN_HARDWARE) { UpdateMousePosition(ScreenInfo, InputEvent.Event.MouseEvent.dwMousePosition); } #endif return FALSE; } VOID HandleFocusEvent( IN PCONSOLE_INFORMATION Console, IN BOOL bSetFocus ) { INPUT_RECORD InputEvent; ULONG EventsWritten; USERTHREAD_FLAGS Flags; InputEvent.EventType = FOCUS_EVENT; InputEvent.Event.FocusEvent.bSetFocus = bSetFocus; Flags.dwFlags = 0; if (bSetFocus) { if (Console->Flags & CONSOLE_VDM_REGISTERED) { Flags.dwFlags |= TIF_VDMAPP; } if (Console->Flags & CONSOLE_CONNECTED_TO_EMULATOR) { Flags.dwFlags |= TIF_DOSEMULATOR; } } Flags.dwMask = (TIF_VDMAPP | TIF_DOSEMULATOR); NtUserSetInformationThread(Console->InputThreadInfo->ThreadHandle, UserThreadFlags, &Flags, sizeof(Flags)); EventsWritten = WriteInputBuffer( Console, &Console->InputBuffer, &InputEvent, 1 ); #if DBG if (EventsWritten != 1) { DbgPrint("PutInputInBuffer: EventsWritten != 1, 1 expected\n"); } #endif } VOID HandleMenuEvent( IN PCONSOLE_INFORMATION Console, IN DWORD wParam ) { INPUT_RECORD InputEvent; ULONG EventsWritten; InputEvent.EventType = MENU_EVENT; InputEvent.Event.MenuEvent.dwCommandId = wParam; EventsWritten = WriteInputBuffer( Console, &Console->InputBuffer, &InputEvent, 1 ); #if DBG if (EventsWritten != 1) { DbgPrint("PutInputInBuffer: EventsWritten != 1, 1 expected\n"); } #endif } VOID HandleCtrlEvent( IN PCONSOLE_INFORMATION Console, IN DWORD EventType ) { switch (EventType) { case CTRL_C_EVENT: Console->CtrlFlags |= CONSOLE_CTRL_C_FLAG; break; case CTRL_BREAK_EVENT: Console->CtrlFlags |= CONSOLE_CTRL_BREAK_FLAG; break; case CTRL_CLOSE_EVENT: Console->CtrlFlags |= CONSOLE_CTRL_CLOSE_FLAG; break; default: RIPMSG1(RIP_ERROR, "Invalid EventType: 0x%x", EventType); } } VOID KillProcess( PCONSOLE_PROCESS_TERMINATION_RECORD ProcessHandleRecord, ULONG_PTR ProcessId ) { NTSTATUS status; // // Just terminate the process outright. // status = NtTerminateProcess(ProcessHandleRecord->ProcessHandle, ProcessHandleRecord->bDebugee ? DBG_TERMINATE_PROCESS : CONTROL_C_EXIT); #if DBG if (status != STATUS_SUCCESS && status != STATUS_PROCESS_IS_TERMINATING && status != STATUS_THREAD_WAS_SUSPENDED && !(status == STATUS_ACCESS_DENIED && ProcessHandleRecord->bDebugee)) { DbgPrint("NtTerminateProcess failed - status = %x\n", status); DbgBreakPoint(); } #endif // // Clear any remaining hard errors for the process. // if (ProcessId) BoostHardError(ProcessId, BHE_FORCE); // // Give the process 5 seconds to exit. // if (NT_SUCCESS(status)) { LARGE_INTEGER li; li.QuadPart = (LONGLONG)-10000 * CMSHUNGAPPTIMEOUT; status = NtWaitForSingleObject(ProcessHandleRecord->ProcessHandle, FALSE, &li); if (status != STATUS_WAIT_0) { RIPMSG2(RIP_WARNING, "KillProcess: wait for process %x failed with status %x", ProcessId, status); } } } int CreateCtrlThread( IN PCONSOLE_PROCESS_TERMINATION_RECORD ProcessHandleList, IN ULONG ProcessHandleListLength, IN PWCHAR Title, IN DWORD EventType, IN BOOL fForce) { HANDLE Thread; DWORD Status; NTSTATUS status; DWORD ShutdownFlags; int Success=CONSOLE_SHUTDOWN_SUCCEEDED; ULONG i; DWORD EventFlags; PROCESS_BASIC_INFORMATION BasicInfo; PCSR_PROCESS Process; BOOL fForceProcess; BOOL fExitProcess; BOOL fFirstPass=TRUE; BOOL fSecondPassNeeded=FALSE; BOOL fHasError; BOOL fFirstWait; BOOL fEventProcessed; BOOL fBreakEvent; BigLoop: for (i = 0; i < ProcessHandleListLength; i++) { // // If the user has already cancelled shutdown, don't try to kill // any more processes. // if (Success == CONSOLE_SHUTDOWN_FAILED) { break; } // // Get the process shutdown parameters here. First get the process // id so we can get the csr process structure pointer. // status = NtQueryInformationProcess(ProcessHandleList[i].ProcessHandle, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); // // Grab the shutdown flags from the csr process structure. If // the structure cannot be found, terminate the process. // ProcessHandleList[i].bDebugee = FALSE; ShutdownFlags = 0; if (NT_SUCCESS(status)) { CsrLockProcessByClientId((HANDLE)BasicInfo.UniqueProcessId, &Process); if (Process == NULL) { KillProcess(&ProcessHandleList[i], BasicInfo.UniqueProcessId); continue; } } else { KillProcess(&ProcessHandleList[i], 0); continue; } ShutdownFlags = Process->ShutdownFlags; ProcessHandleList[i].bDebugee = Process->DebugUserInterface.UniqueProcess!=NULL; CsrUnlockProcess(Process); if (!ProcessHandleList[i].bDebugee) { HANDLE DebugPort = NULL; // // See if we're a console app that's being debugged. // status = NtQueryInformationProcess( ProcessHandleList[i].ProcessHandle, ProcessDebugPort, (PVOID)&DebugPort, sizeof(DebugPort), NULL ); if (NT_SUCCESS(status) && DebugPort) { ProcessHandleList[i].bDebugee = TRUE; } } if (EventType != CTRL_C_EVENT && EventType != CTRL_BREAK_EVENT) { fBreakEvent = FALSE; if (fFirstPass) { if (ProcessHandleList[i].bDebugee) { fSecondPassNeeded = TRUE; continue; } } else { if (!ProcessHandleList[i].bDebugee) { continue; } } } else { fBreakEvent = TRUE; fFirstPass=FALSE; } // // fForce is whether ExitWindowsEx was called with EWX_FORCE. // ShutdownFlags are the shutdown flags for this process. If // either are force (noretry is the same as force), then force: // which means if the app doesn't exit, don't bring up the retry // dialog - just force it to exit right away. // fForceProcess = fForce || (ShutdownFlags & SHUTDOWN_NORETRY); // // Only notify system security and service context processes. // Don't bring up retry dialogs for them. // fExitProcess = TRUE; EventFlags = 0; if (ShutdownFlags & (SHUTDOWN_SYSTEMCONTEXT | SHUTDOWN_OTHERCONTEXT)) { // // System context - make sure we don't cause it to exit, make // sure we don't bring up retry dialogs. // fExitProcess = FALSE; fForceProcess = TRUE; // // This EventFlag will be passed on down to the CtrlRoutine() // on the client side. That way that side knows not to exit // this process. // EventFlags = 0x80000000; } // // Is this the first time we're waiting for this process to die? // fFirstWait = TRUE; fEventProcessed = FALSE; while (!fEventProcessed) { DWORD ThreadExitCode; DWORD ProcessExitCode; DWORD cMsTimeout; Thread = InternalCreateCallbackThread(ProcessHandleList[i].ProcessHandle, (ULONG_PTR)ProcessHandleList[i].CtrlRoutine, EventType | EventFlags); // // If the thread cannot be created, terminate the process. // if (Thread == NULL) { RIPMSG1(RIP_WARNING, "CreateRemoteThread failed 0x%x", GetLastError()); break; } // // Mark the event as processed. // fEventProcessed = TRUE; /* * if it's a ctrl-c or ctrl-break event, just close our * handle to the thread. otherwise it's a close. wait * for client-side thread to terminate. */ if (EventType == CTRL_CLOSE_EVENT) { cMsTimeout = gCmsHungAppTimeout; } else if (EventType == CTRL_LOGOFF_EVENT) { cMsTimeout = gCmsWaitToKillTimeout; } else if (EventType == CTRL_SHUTDOWN_EVENT) { // // If we are shutting down services.exe, we need to look in the // registry to see how long to wait. // if (fFirstWait && BasicInfo.UniqueProcessId == gdwServicesProcessId) { cMsTimeout = gdwServicesWaitToKillTimeout; } else { cMsTimeout = gCmsWaitToKillTimeout; } } else { CloseHandle(Thread); fExitProcess = FALSE; break; } while (TRUE) { fHasError = BoostHardError(BasicInfo.UniqueProcessId, (fForceProcess ? BHE_FORCE : BHE_ACTIVATE)); // // Use a 1 second wait if there was a hard error, otherwise // wait cMsTimeout ms. // Status = InternalWaitCancel(Thread, (fHasError && fForceProcess) ? 1000 : cMsTimeout); if (Status == WAIT_TIMEOUT) { int Action; // // If there was a hard error, see if there is another one. // if (fHasError && fForceProcess) { continue; } if (!fForceProcess) { // // we timed out in the handler. ask the user what // to do. // DialogBoxCount++; Action = ThreadShutdownNotify(WMCS_CONSOLE, (ULONG_PTR)Thread, (LPARAM)Title); DialogBoxCount--; // // If the response is Cancel or EndTask, exit the loop. // Otherwise retry the wait. // if (Action == TSN_USERSAYSCANCEL) { Success = CONSOLE_SHUTDOWN_FAILED; } } } else if (Status == 0) { ThreadExitCode = 0; GetExitCodeThread(Thread,&ThreadExitCode); GetExitCodeProcess(ProcessHandleList[i].ProcessHandle, &ProcessExitCode); // // if the app returned TRUE (event handled) // notify the user and see if the app should // be terminated anyway. // if (fHasError || (ThreadExitCode == EventType && ProcessExitCode == STILL_ACTIVE)) { int Action; if (!fForceProcess) { // // Wait for the process to exit. If it does exit, // don't bring up the end task dialog. // Status = InternalWaitCancel(ProcessHandleList[i].ProcessHandle, (fHasError || fFirstWait) ? 1000 : cMsTimeout); if (Status == 0) { // // The process exited. // fExitProcess = FALSE; } else if (Status == WAIT_TIMEOUT) { DialogBoxCount++; Action = ThreadShutdownNotify(WMCS_CONSOLE, (ULONG_PTR)ProcessHandleList[i].ProcessHandle, (LPARAM)Title); DialogBoxCount--; if (Action == TSN_USERSAYSCANCEL) { Success = CONSOLE_SHUTDOWN_FAILED; } } } } else { // // The process exited. // fExitProcess = FALSE; } } // // If we get here, we know that all wait conditions have // been satisfied. Time to finish with the process. // break; } CloseHandle(Thread); } // // If the process is shutting down, mark it as terminated. // This prevents the process from raising any hard error popups // after we're done shutting it down. // if (!fBreakEvent && !(ShutdownFlags & (SHUTDOWN_SYSTEMCONTEXT | SHUTDOWN_OTHERCONTEXT)) && Success == CONSOLE_SHUTDOWN_SUCCEEDED) { CsrLockProcessByClientId( (HANDLE)BasicInfo.UniqueProcessId, &Process); if (Process) { Process->Flags |= CSR_PROCESS_TERMINATED; CsrUnlockProcess(Process); } // // Force the termination of the process if needed. Otherwise, // acknowledge any remaining hard errors. // if (fExitProcess) { KillProcess(&ProcessHandleList[i], BasicInfo.UniqueProcessId); } else { BoostHardError(BasicInfo.UniqueProcessId, BHE_FORCE); } } } // // If this was our first time through and we skipped one of the // processes because it was being debugged, we'll go back for a // second pass. // if (fFirstPass && fSecondPassNeeded) { fFirstPass = FALSE; goto BigLoop; } // if we're shutting down a system or service security context // thread, don't wait for the process to terminate if (ShutdownFlags & (SHUTDOWN_SYSTEMCONTEXT | SHUTDOWN_OTHERCONTEXT)) { return CONSOLE_SHUTDOWN_SYSTEM; } return Success; } int ProcessCtrlEvents( IN PCONSOLE_INFORMATION Console ) /* returns TRUE if a ctrl thread was created */ { PWCHAR Title; CONSOLE_PROCESS_TERMINATION_RECORD ProcessHandles[2]; PCONSOLE_PROCESS_TERMINATION_RECORD ProcessHandleList; ULONG ProcessHandleListLength,i; ULONG CtrlFlags; PLIST_ENTRY ListHead, ListNext; BOOL FreeTitle; int Success; PCONSOLE_PROCESS_HANDLE ProcessHandleRecord; DWORD EventType; DWORD LimitingProcessId; NTSTATUS Status; // // If the console was marked for destruction, do it now. // if (Console->Flags & CONSOLE_IN_DESTRUCTION) { DestroyConsole(Console); return CONSOLE_SHUTDOWN_FAILED; } // // make sure we don't try to process control events if this // console is already going away // if (Console->Flags & CONSOLE_TERMINATING) { Console->CtrlFlags = 0; } if (Console->CtrlFlags == 0) { RtlLeaveCriticalSection(&Console->ConsoleLock); return CONSOLE_SHUTDOWN_FAILED; } // // make our own copy of the console process handle list // LimitingProcessId = Console->LimitingProcessId; Console->LimitingProcessId = 0; ListHead = &Console->ProcessHandleList; ListNext = ListHead->Flink; ProcessHandleListLength = 0; while (ListNext != ListHead) { ProcessHandleRecord = CONTAINING_RECORD( ListNext, CONSOLE_PROCESS_HANDLE, ListLink ); ListNext = ListNext->Flink; if ( LimitingProcessId ) { if ( ProcessHandleRecord->Process->ProcessGroupId == LimitingProcessId ) { ProcessHandleListLength += 1; } } else { ProcessHandleListLength += 1; } } // // Use the stack buffer to hold the process handles if there are only a // few, otherwise allocate a buffer from the heap. // if (ProcessHandleListLength <= ARRAY_SIZE(ProcessHandles)) { ProcessHandleList = ProcessHandles; } else { ProcessHandleList = ConsoleHeapAlloc(TMP_TAG, ProcessHandleListLength * sizeof(CONSOLE_PROCESS_TERMINATION_RECORD)); if (ProcessHandleList == NULL) { RtlLeaveCriticalSection(&Console->ConsoleLock); return CONSOLE_SHUTDOWN_FAILED; } } ListNext = ListHead->Flink; i = 0; while (ListNext != ListHead) { BOOLEAN ProcessIsIn; UserAssert(i <= ProcessHandleListLength); ProcessHandleRecord = CONTAINING_RECORD(ListNext, CONSOLE_PROCESS_HANDLE, ListLink); ListNext = ListNext->Flink; if (LimitingProcessId) { if (ProcessHandleRecord->Process->ProcessGroupId == LimitingProcessId) { ProcessIsIn = TRUE; } else { ProcessIsIn = FALSE; } } else { ProcessIsIn = TRUE; } if (ProcessIsIn) { Success = (int)DuplicateHandle(NtCurrentProcess(), ProcessHandleRecord->ProcessHandle, NtCurrentProcess(), &ProcessHandleList[i].ProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); // // If the duplicate failed, the best we can do is to skip // including the process in the list and hope it goes // away. // if (!Success) { RIPMSG3(RIP_WARNING, "Dup handle failed for %d of %d in 0x%p", i, ProcessHandleListLength, Console); continue; } if (Console->CtrlFlags & CONSOLE_CTRL_CLOSE_FLAG) { ProcessHandleRecord->TerminateCount++; } else { ProcessHandleRecord->TerminateCount = 0; } ProcessHandleList[i].TerminateCount = ProcessHandleRecord->TerminateCount; if (ProcessHandleRecord->CtrlRoutine) { ProcessHandleList[i].CtrlRoutine = ProcessHandleRecord->CtrlRoutine; } else { ProcessHandleList[i].CtrlRoutine = CtrlRoutine; } // // If this is the VDM process and we're closing the // console window, move it to the front of the list // if (i > 0 && Console->VDMProcessId && Console->VDMProcessId == ProcessHandleRecord->Process->ClientId.UniqueProcess && ProcessHandleRecord->TerminateCount > 0) { CONSOLE_PROCESS_TERMINATION_RECORD ProcessHandle; ProcessHandle = ProcessHandleList[0]; ProcessHandleList[0] = ProcessHandleList[i]; ProcessHandleList[i] = ProcessHandle; } i++; } } ProcessHandleListLength = i; UserAssert(ProcessHandleListLength > 0); // // Copy title. titlelength does not include terminating null. // Title = ConsoleHeapAlloc(TITLE_TAG, Console->TitleLength + sizeof(WCHAR)); if (Title) { FreeTitle = TRUE; RtlCopyMemory(Title,Console->Title,Console->TitleLength+sizeof(WCHAR)); } else { FreeTitle = FALSE; Title = L"Command Window"; } // // Copy ctrl flags. // CtrlFlags = Console->CtrlFlags; UserAssert( !((CtrlFlags & (CONSOLE_CTRL_CLOSE_FLAG | CONSOLE_CTRL_BREAK_FLAG | CONSOLE_CTRL_C_FLAG)) && (CtrlFlags & (CONSOLE_CTRL_LOGOFF_FLAG | CONSOLE_CTRL_SHUTDOWN_FLAG)) )); Console->CtrlFlags = 0; RtlLeaveCriticalSection(&Console->ConsoleLock); // // the ctrl flags could be a combination of the following // values: // // CONSOLE_CTRL_C_FLAG // CONSOLE_CTRL_BREAK_FLAG // CONSOLE_CTRL_CLOSE_FLAG // CONSOLE_CTRL_LOGOFF_FLAG // CONSOLE_CTRL_SHUTDOWN_FLAG // Success = CONSOLE_SHUTDOWN_FAILED; EventType = (DWORD)-1; switch (CtrlFlags & (CONSOLE_CTRL_CLOSE_FLAG | CONSOLE_CTRL_BREAK_FLAG | CONSOLE_CTRL_C_FLAG | CONSOLE_CTRL_LOGOFF_FLAG | CONSOLE_CTRL_SHUTDOWN_FLAG)) { case CONSOLE_CTRL_CLOSE_FLAG: EventType = CTRL_CLOSE_EVENT; break; case CONSOLE_CTRL_BREAK_FLAG: EventType = CTRL_BREAK_EVENT; break; case CONSOLE_CTRL_C_FLAG: EventType = CTRL_C_EVENT; break; case CONSOLE_CTRL_LOGOFF_FLAG: EventType = CTRL_LOGOFF_EVENT; break; case CONSOLE_CTRL_SHUTDOWN_FLAG: EventType = CTRL_SHUTDOWN_EVENT; break; } if (EventType != (DWORD)-1) { Success = CreateCtrlThread(ProcessHandleList, ProcessHandleListLength, Title, EventType, (CtrlFlags & CONSOLE_FORCE_SHUTDOWN_FLAG) != 0 ); } if (FreeTitle) { ConsoleHeapFree(Title); } for (i=0;iUnlockConsoleSkipCount != 0) { if (Console->UnlockConsoleOwningThread == NtCurrentTeb()->ClientId.UniqueThread) { Console->UnlockConsoleSkipCount--; return; } } // // do nothing if we are in screen switching(handshaking with ntvdm) // we don't check anything else because we are in a safe state here. // if (ConsoleVDMOnSwitching == Console && ConsoleVDMOnSwitching->VDMProcessId == CONSOLE_CLIENTPROCESSID()) { RIPMSG1(RIP_WARNING, " UnlockConsole - Thread %lx is leaving VDM CritSec", GetCurrentThreadId()); RtlLeaveCriticalSection(&ConsoleVDMCriticalSection); return; } #endif // // if we're about to release the console lock, see if there // are any satisfied wait blocks that need to be dereferenced. // this code avoids a deadlock between grabbing the console // lock and then grabbing the process structure lock. // #if defined(_X86_) || defined(_AMD64_) if (Console->ConsoleLock.RecursionCount == 1) { #endif #if defined(_IA64_) if (Console->ConsoleLock.RecursionCount == 0) { #endif InitializeListHead(&WaitQueue); if (Console->WaitQueue) { CsrMoveSatisfiedWait(&WaitQueue, Console->WaitQueue); Console->WaitQueue = NULL; } ProcessCtrlEvents(Console); /* * Can't call CsrDereferenceWait with the console locked or we could deadlock. */ if (!IsListEmpty(&WaitQueue)) { CsrDereferenceWait(&WaitQueue); } } else { RtlLeaveCriticalSection(&Console->ConsoleLock); } } ULONG ShutdownConsole( IN HANDLE ConsoleHandle, IN DWORD dwFlags ) /* returns TRUE if console shutdown. we recurse here so we don't return from the WM_QUERYENDSESSION until the console is gone. */ { DWORD EventFlag; int WaitForShutdown; PCONSOLE_INFORMATION Console; EventFlag = 0; // // Transmit the force bit (meaning don't bring up the retry dialog // if the app times out. // if (dwFlags & EWX_FORCE) EventFlag |= CONSOLE_FORCE_SHUTDOWN_FLAG; // // Remember if this is shutdown or logoff - inquiring apps want to know. // if (dwFlags & EWX_SHUTDOWN) { EventFlag |= CONSOLE_CTRL_SHUTDOWN_FLAG; } else { EventFlag |= CONSOLE_CTRL_LOGOFF_FLAG; } // // see if console already going away // if (!NT_SUCCESS(RevalidateConsole(ConsoleHandle, &Console))) { RIPMSG0(RIP_WARNING, "Shutting down terminating console"); return SHUTDOWN_KNOWN_PROCESS; } Console->Flags |= CONSOLE_SHUTTING_DOWN; Console->CtrlFlags = EventFlag; Console->LimitingProcessId = 0; WaitForShutdown = ProcessCtrlEvents(Console); if (WaitForShutdown == CONSOLE_SHUTDOWN_SUCCEEDED) { return (ULONG)STATUS_PROCESS_IS_TERMINATING; } else { if (!NT_SUCCESS(RevalidateConsole(ConsoleHandle, &Console))) { return SHUTDOWN_KNOWN_PROCESS; } Console->Flags &= ~CONSOLE_SHUTTING_DOWN; UnlockConsole(Console); if (WaitForShutdown == CONSOLE_SHUTDOWN_SYSTEM) { return SHUTDOWN_KNOWN_PROCESS; } else { return SHUTDOWN_CANCEL; } } } /* * Exit routine for threads created with RtlCreateUserThread. These threads * cannot call ExitThread(). So don't do that. * */ VOID UserExitWorkerThread( NTSTATUS Status) { NtCurrentTeb()->FreeStackOnTermination = TRUE; NtTerminateThread(NtCurrentThread(), Status); }