// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "popup.h" #include "_output.h" #include "output.h" #include "dbcs.h" #include "srvinit.h" #include "stream.h" #include "resource.h" #include "utils.hpp" #include "../interactivity/inc/ServiceLocator.hpp" #pragma hdrstop using namespace Microsoft::Console::Types; using Microsoft::Console::Interactivity::ServiceLocator; // Routine Description: // - Creates an object representing an interactive popup overlay during cooked mode command line editing. // - NOTE: Modifies global popup count (and adjusts cursor visibility as appropriate.) // Arguments: // - screenInfo - Reference to screen on which the popup should be drawn/overlayed. // - proposedSize - Suggested size of the popup. May be adjusted based on screen size. Popup::Popup(SCREEN_INFORMATION& screenInfo, const COORD proposedSize) : _screenInfo(screenInfo), _userInputFunction(&Popup::_getUserInputInternal) { _attributes = screenInfo.GetPopupAttributes(); const COORD size = _CalculateSize(screenInfo, proposedSize); const COORD origin = _CalculateOrigin(screenInfo, size); _region.Left = origin.X; _region.Top = origin.Y; _region.Right = gsl::narrow(origin.X + size.X - 1i16); _region.Bottom = gsl::narrow(origin.Y + size.Y - 1i16); _oldScreenSize = screenInfo.GetBufferSize().Dimensions(); SMALL_RECT TargetRect; TargetRect.Left = 0; TargetRect.Top = _region.Top; TargetRect.Right = _oldScreenSize.X - 1; TargetRect.Bottom = _region.Bottom; // copy the data into the backup buffer _oldContents = std::move(screenInfo.ReadRect(Viewport::FromInclusive(TargetRect))); CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto countWas = gci.PopupCount.fetch_add(1ui16); if (0 == countWas) { // If this is the first popup to be shown, stop the cursor from appearing/blinking screenInfo.GetTextBuffer().GetCursor().SetIsPopupShown(true); } } // Routine Description: // - Cleans up a popup object // - NOTE: Modifies global popup count (and adjusts cursor visibility as appropriate.) Popup::~Popup() { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto countWas = gci.PopupCount.fetch_sub(1i16); if (1 == countWas) { // Notify we're done showing popups. gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetIsPopupShown(false); } } void Popup::Draw() { _DrawBorder(); _DrawContent(); } // Routine Description: // - Draws the outlines of the popup area in the screen buffer void Popup::_DrawBorder() { // fill attributes of top line COORD WriteCoord; WriteCoord.X = _region.Left; WriteCoord.Y = _region.Top; _screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord); // draw upper left corner _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT, 1), WriteCoord); // draw upper bar WriteCoord.X += 1; _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord); // draw upper right corner WriteCoord.X = _region.Right; _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT, 1), WriteCoord); for (SHORT i = 0; i < Height(); i++) { WriteCoord.Y += 1; WriteCoord.X = _region.Left; // fill attributes _screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord); _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord); WriteCoord.X = _region.Right; _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord); } // Draw bottom line. // Fill attributes of top line. WriteCoord.X = _region.Left; WriteCoord.Y = _region.Bottom; _screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord); // Draw bottom left corner. WriteCoord.X = _region.Left; _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT, 1), WriteCoord); // Draw lower bar. WriteCoord.X += 1; _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord); // draw lower right corner WriteCoord.X = _region.Right; _screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT, 1), WriteCoord); } // Routine Description: // - Draws prompt information in the popup area to tell the user what to enter. // Arguments: // - id - Resource ID for string to display to user void Popup::_DrawPrompt(const UINT id) { std::wstring text = _LoadString(id); // Draw empty popup. COORD WriteCoord; WriteCoord.X = _region.Left + 1i16; WriteCoord.Y = _region.Top + 1i16; size_t lStringLength = Width(); for (SHORT i = 0; i < Height(); i++) { const OutputCellIterator it(UNICODE_SPACE, _attributes, lStringLength); const auto done = _screenInfo.Write(it, WriteCoord); lStringLength = done.GetCellDistance(it); WriteCoord.Y += 1; } WriteCoord.X = _region.Left + 1i16; WriteCoord.Y = _region.Top + 1i16; // write prompt to screen lStringLength = text.size(); if (lStringLength > (ULONG)Width()) { text = text.substr(0, Width()); } size_t used; LOG_IF_FAILED(ServiceLocator::LocateGlobals().api.WriteConsoleOutputCharacterWImpl(_screenInfo, text, WriteCoord, used)); } // Routine Description: // - Cleans up a popup by restoring the stored buffer information to the region of // the screen that the popup was covering and frees resources. void Popup::End() { // restore previous contents to screen SMALL_RECT SourceRect; SourceRect.Left = 0i16; SourceRect.Top = _region.Top; SourceRect.Right = _oldScreenSize.X - 1i16; SourceRect.Bottom = _region.Bottom; const auto sourceViewport = Viewport::FromInclusive(SourceRect); _screenInfo.WriteRect(_oldContents, sourceViewport.Origin()); } // Routine Description: // - Helper to calculate the size of the popup. // Arguments: // - screenInfo - Screen buffer we will be drawing into // - proposedSize - The suggested size of the popup that may need to be adjusted to fit // Return Value: // - Coordinate size that the popup should consume in the screen buffer COORD Popup::_CalculateSize(const SCREEN_INFORMATION& screenInfo, const COORD proposedSize) { // determine popup dimensions COORD size = proposedSize; size.X += 2; // add borders size.Y += 2; // add borders const COORD viewportSize = screenInfo.GetViewport().Dimensions(); size.X = std::min(size.X, viewportSize.X); size.Y = std::min(size.Y, viewportSize.Y); // make sure there's enough room for the popup borders THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, size.X < 2 || size.Y < 2); return size; } // Routine Description: // - Helper to calculate the origin point (within the screen buffer) for the popup // Arguments: // - screenInfo - Screen buffer we will be drawing into // - size - The size that the popup will consume // Return Value: // - Coordinate position of the origin point of the popup COORD Popup::_CalculateOrigin(const SCREEN_INFORMATION& screenInfo, const COORD size) { const auto viewport = screenInfo.GetViewport(); // determine origin. center popup on window COORD origin; origin.X = gsl::narrow((viewport.Width() - size.X) / 2 + viewport.Left()); origin.Y = gsl::narrow((viewport.Height() - size.Y) / 2 + viewport.Top()); return origin; } // Routine Description: // - Helper to return the width of the popup in columns // Return Value: // - Width of popup inside attached screen buffer. SHORT Popup::Width() const noexcept { return _region.Right - _region.Left - 1i16; } // Routine Description: // - Helper to return the height of the popup in columns // Return Value: // - Height of popup inside attached screen buffer. SHORT Popup::Height() const noexcept { return _region.Bottom - _region.Top - 1i16; } // Routine Description: // - Helper to get the position on top of some types of popup dialogs where // we should overlay the cursor for user input. // Return Value: // - Coordinate location on the popup where the cursor should be placed. COORD Popup::GetCursorPosition() const noexcept { COORD CursorPosition; CursorPosition.X = _region.Right - static_cast(MINIMUM_COMMAND_PROMPT_SIZE); CursorPosition.Y = _region.Top + 1i16; return CursorPosition; } // Routine Description: // - changes the function used to gather user input. for allowing custom input during unit tests only // Arguments: // - function - function to use to fetch input void Popup::SetUserInputFunction(UserInputFunction function) noexcept { _userInputFunction = function; } // Routine Description: // - gets a single char input from the user // Arguments: // - cookedReadData - cookedReadData for this popup operation // - popupKey - on completion, will be true if key was a popup key // - wch - on completion, the char read from the user // Return Value: // - relevant NTSTATUS [[nodiscard]] NTSTATUS Popup::_getUserInput(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept { return _userInputFunction(cookedReadData, popupKey, modifiers, wch); } // Routine Description: // - gets a single char input from the user using the InputBuffer // Arguments: // - cookedReadData - cookedReadData for this popup operation // - popupKey - on completion, will be true if key was a popup key // - wch - on completion, the char read from the user // Return Value: // - relevant NTSTATUS [[nodiscard]] NTSTATUS Popup::_getUserInputInternal(COOKED_READ_DATA& cookedReadData, bool& popupKey, DWORD& modifiers, wchar_t& wch) noexcept { InputBuffer* const pInputBuffer = cookedReadData.GetInputBuffer(); NTSTATUS Status = GetChar(pInputBuffer, &wch, true, nullptr, &popupKey, &modifiers); if (!NT_SUCCESS(Status) && Status != CONSOLE_STATUS_WAIT) { cookedReadData.BytesRead() = 0; } return Status; }