346 lines
10 KiB
C++
346 lines
10 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "input.h"
|
|
|
|
#include "dbcs.h"
|
|
#include "stream.h"
|
|
|
|
#include "../terminal/input/terminalInput.hpp"
|
|
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
|
|
#pragma hdrstop
|
|
|
|
#define KEY_ENHANCED 0x01000000
|
|
|
|
using Microsoft::Console::Interactivity::ServiceLocator;
|
|
|
|
bool IsInProcessedInputMode()
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
return (gci.pInputBuffer->InputMode & ENABLE_PROCESSED_INPUT) != 0;
|
|
}
|
|
|
|
bool IsInVirtualTerminalInputMode()
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
return WI_IsFlagSet(gci.pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT);
|
|
}
|
|
|
|
BOOL IsSystemKey(const 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;
|
|
}
|
|
|
|
ULONG GetControlKeyState(const LPARAM lParam)
|
|
{
|
|
ULONG ControlKeyState = 0;
|
|
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_LMENU) & KEY_PRESSED)
|
|
{
|
|
ControlKeyState |= LEFT_ALT_PRESSED;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_RMENU) & KEY_PRESSED)
|
|
{
|
|
ControlKeyState |= RIGHT_ALT_PRESSED;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_LCONTROL) & KEY_PRESSED)
|
|
{
|
|
ControlKeyState |= LEFT_CTRL_PRESSED;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_RCONTROL) & KEY_PRESSED)
|
|
{
|
|
ControlKeyState |= RIGHT_CTRL_PRESSED;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_SHIFT) & KEY_PRESSED)
|
|
{
|
|
ControlKeyState |= SHIFT_PRESSED;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_NUMLOCK) & KEY_TOGGLED)
|
|
{
|
|
ControlKeyState |= NUMLOCK_ON;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_SCROLL) & KEY_TOGGLED)
|
|
{
|
|
ControlKeyState |= SCROLLLOCK_ON;
|
|
}
|
|
if (ServiceLocator::LocateInputServices()->GetKeyState(VK_CAPITAL) & KEY_TOGGLED)
|
|
{
|
|
ControlKeyState |= CAPSLOCK_ON;
|
|
}
|
|
if (lParam & KEY_ENHANCED)
|
|
{
|
|
ControlKeyState |= ENHANCED_KEY;
|
|
}
|
|
|
|
ControlKeyState |= (lParam & ALTNUMPAD_BIT);
|
|
|
|
return ControlKeyState;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - returns true if we're in a mode amenable to us taking over keyboard shortcuts
|
|
bool ShouldTakeOverKeyboardShortcuts()
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
return !gci.GetCtrlKeyShortcutsDisabled() && IsInProcessedInputMode();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - handles key events without reference to Win32 elements.
|
|
void HandleGenericKeyEvent(_In_ KeyEvent keyEvent, const bool generateBreak)
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
bool ContinueProcessing = true;
|
|
|
|
if (keyEvent.IsCtrlPressed() &&
|
|
!keyEvent.IsAltPressed() &&
|
|
keyEvent.IsKeyDown())
|
|
{
|
|
// check for ctrl-c, if in line input mode.
|
|
if (keyEvent.GetVirtualKeyCode() == 'C' && IsInProcessedInputMode())
|
|
{
|
|
HandleCtrlEvent(CTRL_C_EVENT);
|
|
if (gci.PopupCount == 0)
|
|
{
|
|
gci.pInputBuffer->TerminateRead(WaitTerminationReason::CtrlC);
|
|
}
|
|
|
|
if (!(WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED)))
|
|
{
|
|
ContinueProcessing = false;
|
|
}
|
|
}
|
|
|
|
// Check for ctrl-break.
|
|
else if (keyEvent.GetVirtualKeyCode() == VK_CANCEL)
|
|
{
|
|
gci.pInputBuffer->Flush();
|
|
HandleCtrlEvent(CTRL_BREAK_EVENT);
|
|
if (gci.PopupCount == 0)
|
|
{
|
|
gci.pInputBuffer->TerminateRead(WaitTerminationReason::CtrlBreak);
|
|
}
|
|
|
|
if (!(WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED)))
|
|
{
|
|
ContinueProcessing = false;
|
|
}
|
|
}
|
|
|
|
// don't write ctrl-esc to the input buffer
|
|
else if (keyEvent.GetVirtualKeyCode() == VK_ESCAPE)
|
|
{
|
|
ContinueProcessing = false;
|
|
}
|
|
}
|
|
else if (keyEvent.IsAltPressed() &&
|
|
keyEvent.IsKeyDown() &&
|
|
keyEvent.GetVirtualKeyCode() == VK_ESCAPE)
|
|
{
|
|
ContinueProcessing = false;
|
|
}
|
|
|
|
if (ContinueProcessing)
|
|
{
|
|
size_t EventsWritten = 0;
|
|
try
|
|
{
|
|
EventsWritten = gci.pInputBuffer->Write(std::make_unique<KeyEvent>(keyEvent));
|
|
if (EventsWritten && generateBreak)
|
|
{
|
|
keyEvent.SetKeyDown(false);
|
|
EventsWritten = gci.pInputBuffer->Write(std::make_unique<KeyEvent>(keyEvent));
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DBG
|
|
// set to true with a debugger to temporarily disable focus events getting written to the InputBuffer
|
|
volatile bool DisableFocusEvents = false;
|
|
#endif
|
|
|
|
void HandleFocusEvent(const BOOL fSetFocus)
|
|
{
|
|
#ifdef DBG
|
|
if (DisableFocusEvents)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
try
|
|
{
|
|
const size_t EventsWritten = gci.pInputBuffer->Write(std::make_unique<FocusEvent>(!!fSetFocus));
|
|
FAIL_FAST_IF(EventsWritten != 1);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
|
|
void HandleMenuEvent(const DWORD wParam)
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
size_t EventsWritten = 0;
|
|
try
|
|
{
|
|
EventsWritten = gci.pInputBuffer->Write(std::make_unique<MenuEvent>(wParam));
|
|
if (EventsWritten != 1)
|
|
{
|
|
RIPMSG0(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1, 1 expected");
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
|
|
void HandleCtrlEvent(const DWORD EventType)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
switch (EventType)
|
|
{
|
|
case CTRL_C_EVENT:
|
|
gci.CtrlFlags |= CONSOLE_CTRL_C_FLAG;
|
|
break;
|
|
case CTRL_BREAK_EVENT:
|
|
gci.CtrlFlags |= CONSOLE_CTRL_BREAK_FLAG;
|
|
break;
|
|
case CTRL_CLOSE_EVENT:
|
|
gci.CtrlFlags |= CONSOLE_CTRL_CLOSE_FLAG;
|
|
break;
|
|
default:
|
|
RIPMSG1(RIP_ERROR, "Invalid EventType: 0x%x", EventType);
|
|
}
|
|
}
|
|
|
|
void ProcessCtrlEvents()
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
if (gci.CtrlFlags == 0)
|
|
{
|
|
gci.UnlockConsole();
|
|
return;
|
|
}
|
|
|
|
// Make our own copy of the console process handle list.
|
|
DWORD const LimitingProcessId = gci.LimitingProcessId;
|
|
gci.LimitingProcessId = 0;
|
|
|
|
ConsoleProcessTerminationRecord* rgProcessHandleList;
|
|
size_t cProcessHandleList;
|
|
|
|
HRESULT hr = gci.ProcessHandleList
|
|
.GetTerminationRecordsByGroupId(LimitingProcessId,
|
|
WI_IsFlagSet(gci.CtrlFlags,
|
|
CONSOLE_CTRL_CLOSE_FLAG),
|
|
&rgProcessHandleList,
|
|
&cProcessHandleList);
|
|
|
|
if (FAILED(hr) || cProcessHandleList == 0)
|
|
{
|
|
gci.UnlockConsole();
|
|
return;
|
|
}
|
|
|
|
// Copy ctrl flags.
|
|
ULONG CtrlFlags = gci.CtrlFlags;
|
|
FAIL_FAST_IF(!(!((CtrlFlags & (CONSOLE_CTRL_CLOSE_FLAG | CONSOLE_CTRL_BREAK_FLAG | CONSOLE_CTRL_C_FLAG)) && (CtrlFlags & (CONSOLE_CTRL_LOGOFF_FLAG | CONSOLE_CTRL_SHUTDOWN_FLAG)))));
|
|
|
|
gci.CtrlFlags = 0;
|
|
|
|
gci.UnlockConsole();
|
|
|
|
// 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
|
|
|
|
DWORD 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;
|
|
}
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
for (size_t i = 0; i < cProcessHandleList; i++)
|
|
{
|
|
/*
|
|
* Status will be non-successful if a process attached to this console
|
|
* vetoes shutdown. In that case, we don't want to try to kill any more
|
|
* processes, but we do need to make sure we continue looping so we
|
|
* can close any remaining process handles. The exception is if the
|
|
* process is inaccessible, such that we can't even open a handle for
|
|
* query. In this case, use best effort to send the close event but
|
|
* ignore any errors.
|
|
*/
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = ServiceLocator::LocateConsoleControl()
|
|
->EndTask((HANDLE)rgProcessHandleList[i].dwProcessID,
|
|
EventType,
|
|
CtrlFlags);
|
|
if (rgProcessHandleList[i].hProcess == nullptr)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (rgProcessHandleList[i].hProcess != nullptr)
|
|
{
|
|
CloseHandle(rgProcessHandleList[i].hProcess);
|
|
}
|
|
}
|
|
|
|
delete[] rgProcessHandleList;
|
|
}
|