// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "Clipboard.hpp" #include "ConsoleControl.hpp" #include "find.h" #include "menu.hpp" #include "window.hpp" #include "windowdpiapi.hpp" #include "windowime.hpp" #include "windowio.hpp" #include "windowmetrics.hpp" #include "../../host/_output.h" #include "../../host/output.h" #include "../../host/dbcs.h" #include "../../host/handle.h" #include "../../host/input.h" #include "../../host/misc.h" #include "../../host/registry.hpp" #include "../../host/scrolling.hpp" #include "../../host/srvinit.h" #include "../inc/ServiceLocator.hpp" #include "../../inc/conint.h" #include "../interactivity/win32/CustomWindowMessages.h" #include "../interactivity/win32/windowUiaProvider.hpp" #include #include using namespace Microsoft::Console::Interactivity::Win32; using namespace Microsoft::Console::Types; // The static and specific window procedures for this class are contained here #pragma region Window Procedure [[nodiscard]] LRESULT CALLBACK Window::s_ConsoleWindowProc(_In_ HWND hWnd, _In_ UINT Message, _In_ WPARAM wParam, _In_ LPARAM lParam) { // Save the pointer here to the specific window instance when one is created if (Message == WM_CREATE) { const CREATESTRUCT* const pCreateStruct = reinterpret_cast(lParam); Window* const pWindow = reinterpret_cast(pCreateStruct->lpCreateParams); SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pWindow)); } // Dispatch the message to the specific class instance Window* const pWindow = reinterpret_cast(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); if (pWindow != nullptr) { return pWindow->ConsoleWindowProc(hWnd, Message, wParam, lParam); } // If we get this far, call the default window proc return DefWindowProcW(hWnd, Message, wParam, lParam); } [[nodiscard]] LRESULT CALLBACK Window::ConsoleWindowProc(_In_ HWND hWnd, _In_ UINT Message, _In_ WPARAM wParam, _In_ LPARAM lParam) { Globals& g = ServiceLocator::LocateGlobals(); CONSOLE_INFORMATION& gci = g.getConsoleInformation(); LRESULT Status = 0; BOOL Unlock = TRUE; LockConsole(); SCREEN_INFORMATION& ScreenInfo = GetScreenInfo(); if (hWnd == nullptr) // TODO: this might not be possible anymore { if (Message == WM_CLOSE) { _CloseWindow(); Status = 0; } else { Status = DefWindowProcW(hWnd, Message, wParam, lParam); } UnlockConsole(); return Status; } switch (Message) { case WM_CREATE: { // Load all metrics we'll need. _UpdateSystemMetrics(); // The system is not great and the window rect is wrong the first time for High DPI (WM_DPICHANGED scales strangely.) // So here we have to grab the DPI of the current window (now that we have a window). // Then we have to re-propose a window size for our window that is scaled to DPI and SetWindowPos. // First get the new DPI and update all the scaling factors in the console that are affected. // NOTE: GetDpiForWindow can be *WRONG* at this point in time depending on monitor configuration. // They won't be correct until the window is actually shown. So instead of using those APIs, figure out the DPI // based on the rectangle that is about to be shown using the nearest monitor. // Get proposed window rect from create structure CREATESTRUCTW* pcs = (CREATESTRUCTW*)lParam; RECT rc; rc.left = pcs->x; rc.top = pcs->y; rc.right = rc.left + pcs->cx; rc.bottom = rc.top + pcs->cy; // Find nearest monitor. HMONITOR hmon = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); // This API guarantees that dpix and dpiy will be equal, but neither is an optional parameter so give two UINTs. UINT dpix = USER_DEFAULT_SCREEN_DPI; UINT dpiy = USER_DEFAULT_SCREEN_DPI; GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy); // If this fails, we'll use the default of 96. // Pick one and set it to the global DPI. ServiceLocator::LocateGlobals().dpi = (int)dpix; _UpdateSystemMetrics(); // scroll bars and cursors and such. s_ReinitializeFontsForDPIChange(); // font sizes. // Now re-propose the window size with the same origin. RECT rectProposed = { rc.left, rc.top, 0, 0 }; _CalculateWindowRect(_pSettings->GetWindowSize(), &rectProposed); SetWindowPos(hWnd, nullptr, rectProposed.left, rectProposed.top, RECT_WIDTH(&rectProposed), RECT_HEIGHT(&rectProposed), SWP_NOACTIVATE | SWP_NOZORDER); // Save the proposed window rect dimensions here so we can adjust if the system comes back and changes them on what we asked for. ServiceLocator::LocateWindowMetrics()->ConvertWindowRectToClientRect(&rectProposed); break; } case WM_DROPFILES: { _HandleDrop(wParam); break; } case WM_GETOBJECT: { Status = _HandleGetObject(hWnd, wParam, lParam); break; } case WM_DESTROY: { // signal to uia that they can disconnect our uia provider if (_pUiaProvider) { UiaReturnRawElementProvider(hWnd, 0, 0, nullptr); } break; } case WM_SIZING: { // Signal that the user changed the window size, so we can return the value later for telemetry. By only // sending the data back if the size has changed, helps reduce the amount of telemetry being sent back. // WM_SIZING doesn't fire if they resize the window using Win-UpArrow, so we'll miss that scenario. We could // listen to the WM_SIZE message instead, but they can fire when the window is being restored from being // minimized, and not only when they resize the window. Telemetry::Instance().SetWindowSizeChanged(); goto CallDefWin; break; } case WM_GETDPISCALEDSIZE: { // This message will send us the DPI we're about to be changed to. // Our goal is to use it to try to figure out the Window Rect that we'll need at that DPI to maintain // the same client rendering that we have now. // First retrieve the new DPI and the current DPI. DWORD const dpiProposed = (WORD)wParam; // Now we need to get what the font size *would be* if we had this new DPI. We need to ask the renderer about that. const FontInfo& fiCurrent = ScreenInfo.GetCurrentFont(); FontInfoDesired fiDesired(fiCurrent); FontInfo fiProposed(L"", 0, 0, { 0, 0 }, 0); const HRESULT hr = g.pRender->GetProposedFont(dpiProposed, fiDesired, fiProposed); // fiProposal will be updated by the renderer for this new font. // GetProposedFont can fail if there's no render engine yet. // This can happen if we're headless. // Just assume that the font is 1x1 in that case. const COORD coordFontProposed = SUCCEEDED(hr) ? fiProposed.GetSize() : COORD({ 1, 1 }); // Then from that font size, we need to calculate the client area. // Then from the client area we need to calculate the window area (using the proposed DPI scalar here as well.) // Retrieve the additional parameters we need for the math call based on the current window & buffer properties. const Viewport viewport = ScreenInfo.GetViewport(); COORD coordWindowInChars = viewport.Dimensions(); const COORD coordBufferSize = ScreenInfo.GetTextBuffer().GetSize().Dimensions(); // Now call the math calculation for our proposed size. RECT rectProposed = { 0 }; s_CalculateWindowRect(coordWindowInChars, dpiProposed, coordFontProposed, coordBufferSize, hWnd, &rectProposed); // Prepare where we're going to keep our final suggestion. SIZE* const pSuggestionSize = (SIZE*)lParam; pSuggestionSize->cx = RECT_WIDTH(&rectProposed); pSuggestionSize->cy = RECT_HEIGHT(&rectProposed); // Format our final suggestion for consumption. UnlockConsole(); return TRUE; } case WM_DPICHANGED: { _fInDPIChange = true; ServiceLocator::LocateGlobals().dpi = HIWORD(wParam); _UpdateSystemMetrics(); s_ReinitializeFontsForDPIChange(); // This is the RECT that the system suggests. RECT* const prcNewScale = (RECT*)lParam; SetWindowPos(hWnd, HWND_TOP, prcNewScale->left, prcNewScale->top, RECT_WIDTH(prcNewScale), RECT_HEIGHT(prcNewScale), SWP_NOZORDER | SWP_NOACTIVATE); _fInDPIChange = false; break; } case WM_ACTIVATE: { // if we're activated by a mouse click, remember it so // we don't pass the click on to the app. if (LOWORD(wParam) == WA_CLICKACTIVE) { gci.Flags |= CONSOLE_IGNORE_NEXT_MOUSE_INPUT; } goto CallDefWin; break; } case WM_SETFOCUS: { gci.ProcessHandleList.ModifyConsoleProcessFocus(TRUE); gci.Flags |= CONSOLE_HAS_FOCUS; gci.GetCursorBlinker().FocusStart(); HandleFocusEvent(TRUE); // ActivateTextServices does nothing if already active so this is OK to be called every focus. ActivateTextServices(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), GetImeSuggestionWindowPos, GetTextBoxArea); // set the text area to have focus for accessibility consumers if (_pUiaProvider) { LOG_IF_FAILED(_pUiaProvider->SetTextAreaFocus()); } break; } case WM_KILLFOCUS: { gci.ProcessHandleList.ModifyConsoleProcessFocus(FALSE); gci.Flags &= ~CONSOLE_HAS_FOCUS; // turn it off when we lose focus. gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().SetIsOn(false); gci.GetCursorBlinker().FocusEnd(); HandleFocusEvent(FALSE); break; } case WM_PAINT: { // Since we handle our own minimized window state, we need to // check if we're minimized (iconic) and set our internal state flags accordingly. // http://msdn.microsoft.com/en-us/library/windows/desktop/dd162483(v=vs.85).aspx // NOTE: We will not get called to paint ourselves when minimized because we set an icon when registering the window class. // That means this CONSOLE_IS_ICONIC is unnecessary when/if we can decouple the drawing with D2D. if (IsIconic(hWnd)) { WI_SetFlag(gci.Flags, CONSOLE_IS_ICONIC); } else { WI_ClearFlag(gci.Flags, CONSOLE_IS_ICONIC); } LOG_IF_FAILED(_HandlePaint()); // NOTE: We cannot let the OS handle this message (meaning do NOT pass to DefWindowProc) // or it will cause missing painted regions in scenarios without a DWM (like Core Server SKU). // Ensure it is re-validated in this handler so we don't receive infinite WM_PAINTs after // we have stored the invalid region data for the next trip around the renderer thread. break; } case WM_ERASEBKGND: { break; } case WM_CLOSE: { // Write the final trace log during the WM_CLOSE message while the console process is still fully alive. // This gives us time to query the process for information. We shouldn't really miss any useful // telemetry between now and when the process terminates. Telemetry::Instance().WriteFinalTraceLog(); _CloseWindow(); break; } case WM_SETTINGCHANGE: { LOG_IF_FAILED(Microsoft::Console::Internal::Theming::TrySetDarkMode(hWnd)); gci.GetCursorBlinker().SettingsChanged(); } __fallthrough; case WM_DISPLAYCHANGE: { _UpdateSystemMetrics(); break; } case WM_WINDOWPOSCHANGING: { // Enforce maximum size here instead of WM_GETMINMAXINFO. // If we return it in WM_GETMINMAXINFO, then it will be enforced when snapping across DPI boundaries (bad.) // Retrieve the suggested dimensions and make a rect and size. LPWINDOWPOS lpwpos = (LPWINDOWPOS)lParam; // We only need to apply restrictions if the size is changing. if (!WI_IsFlagSet(lpwpos->flags, SWP_NOSIZE)) { // Figure out the suggested dimensions RECT rcSuggested; rcSuggested.left = lpwpos->x; rcSuggested.top = lpwpos->y; rcSuggested.right = rcSuggested.left + lpwpos->cx; rcSuggested.bottom = rcSuggested.top + lpwpos->cy; SIZE szSuggested; szSuggested.cx = RECT_WIDTH(&rcSuggested); szSuggested.cy = RECT_HEIGHT(&rcSuggested); // Figure out the current dimensions for comparison. RECT rcCurrent = GetWindowRect(); // Determine whether we're being resized by someone dragging the edge or completely moved around. bool fIsEdgeResize = false; { // We can only be edge resizing if our existing rectangle wasn't empty. If it was empty, we're doing the initial create. if (!IsRectEmpty(&rcCurrent)) { // If one or two sides are changing, we're being edge resized. unsigned int cSidesChanging = 0; if (rcCurrent.left != rcSuggested.left) { cSidesChanging++; } if (rcCurrent.right != rcSuggested.right) { cSidesChanging++; } if (rcCurrent.top != rcSuggested.top) { cSidesChanging++; } if (rcCurrent.bottom != rcSuggested.bottom) { cSidesChanging++; } if (cSidesChanging == 1 || cSidesChanging == 2) { fIsEdgeResize = true; } } } // If the window is maximized, let it do whatever it wants to do. // If not, then restrict it to our maximum possible window. if (!WI_IsFlagSet(GetWindowStyle(hWnd), WS_MAXIMIZE)) { // Find the related monitor, the maximum pixel size, // and the dpi for the suggested rect. UINT dpiOfMaximum; RECT rcMaximum; if (fIsEdgeResize) { // If someone's dragging from the edge to resize in one direction, we want to make sure we never grow past the current monitor. rcMaximum = ServiceLocator::LocateWindowMetrics()->GetMaxWindowRectInPixels(&rcCurrent, &dpiOfMaximum); } else { // In other circumstances, assume we're snapping around or some other jump (TS). // Just do whatever we're told using the new suggestion as the restriction monitor. rcMaximum = ServiceLocator::LocateWindowMetrics()->GetMaxWindowRectInPixels(&rcSuggested, &dpiOfMaximum); } // Only apply the maximum size restriction if the current DPI matches the DPI of the // maximum rect. This keeps us from applying the wrong restriction if the monitor // we're moving to has a different DPI but we've yet to get notified of that DPI // change. If we do apply it, then we'll restrict the console window BEFORE its // been resized for the DPI change, so we're likely to shrink the window too much // or worse yet, keep it from moving entirely. We'll get a WM_DPICHANGED, // resize the window, and then process the restriction in a few window messages. if (((int)dpiOfMaximum == g.dpi) && ((szSuggested.cx > RECT_WIDTH(&rcMaximum)) || (szSuggested.cy > RECT_HEIGHT(&rcMaximum)))) { lpwpos->cx = std::min(RECT_WIDTH(&rcMaximum), szSuggested.cx); lpwpos->cy = std::min(RECT_HEIGHT(&rcMaximum), szSuggested.cy); // We usually add SWP_NOMOVE so that if the user is dragging the left or top edge // and hits the restriction, then the window just stops growing, it doesn't // move with the mouse. However during DPI changes, we need to allow a move // because the RECT from WM_DPICHANGED has been specially crafted by win32k // to keep the mouse cursor from jumping away from the caption bar. if (!_fInDPIChange) { lpwpos->flags |= SWP_NOMOVE; } } } break; } else { goto CallDefWin; } } case WM_WINDOWPOSCHANGED: { // Only handle this if the DPI is the same as last time. // If the DPI is different, assume we're about to get a DPICHANGED notification // which will have a better suggested rectangle than this one. // NOTE: This stopped being possible in RS4 as the DPI now changes when and only when // we receive WM_DPICHANGED. We keep this check around so that we perform better downlevel. int const dpi = ServiceLocator::LocateHighDpiApi()->GetDpiForWindow(hWnd); if (dpi == ServiceLocator::LocateGlobals().dpi) { _HandleWindowPosChanged(lParam); } break; } case WM_CONTEXTMENU: { Telemetry::Instance().SetContextMenuUsed(); if (DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam) == HTCLIENT) { HMENU hHeirMenu = Menu::s_GetHeirMenuHandle(); Unlock = FALSE; UnlockConsole(); TrackPopupMenuEx(hHeirMenu, TPM_RIGHTBUTTON | (GetSystemMetrics(SM_MENUDROPALIGNMENT) == 0 ? TPM_LEFTALIGN : TPM_RIGHTALIGN), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), hWnd, nullptr); } else { goto CallDefWin; } break; } case WM_NCLBUTTONDOWN: { // allow user to move window even when bigger than the screen switch (wParam & 0x00FF) { case HTCAPTION: UnlockConsole(); Unlock = FALSE; SetActiveWindow(hWnd); SendMessageTimeoutW(hWnd, WM_SYSCOMMAND, SC_MOVE | wParam, lParam, SMTO_NORMAL, INFINITE, nullptr); break; default: goto CallDefWin; } break; } case WM_KEYDOWN: case WM_KEYUP: case WM_CHAR: case WM_DEADCHAR: { HandleKeyEvent(hWnd, Message, wParam, lParam, &Unlock); break; } case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_SYSCHAR: case WM_SYSDEADCHAR: { if (HandleSysKeyEvent(hWnd, Message, wParam, lParam, &Unlock)) { goto CallDefWin; } break; } case WM_COMMAND: // If this is an edit command from the context menu, treat it like a sys command. if ((wParam < ID_CONSOLE_COPY) || (wParam > ID_CONSOLE_SELECTALL)) { break; } __fallthrough; case WM_SYSCOMMAND: if (wParam == ID_CONSOLE_MARK) { Selection::Instance().InitializeMarkSelection(); } else if (wParam == ID_CONSOLE_COPY) { Clipboard::Instance().Copy(); } else if (wParam == ID_CONSOLE_PASTE) { Clipboard::Instance().Paste(); } else if (wParam == ID_CONSOLE_SCROLL) { Scrolling::s_DoScroll(); } else if (wParam == ID_CONSOLE_FIND) { DoFind(); Unlock = FALSE; } else if (wParam == ID_CONSOLE_SELECTALL) { Selection::Instance().SelectAll(); } else if (wParam == ID_CONSOLE_CONTROL) { Menu::s_ShowPropertiesDialog(hWnd, FALSE); } else if (wParam == ID_CONSOLE_DEFAULTS) { Menu::s_ShowPropertiesDialog(hWnd, TRUE); } else { goto CallDefWin; } break; case WM_HSCROLL: { HorizontalScroll(LOWORD(wParam), HIWORD(wParam)); break; } case WM_VSCROLL: { VerticalScroll(LOWORD(wParam), HIWORD(wParam)); break; } case WM_INITMENU: { HandleMenuEvent(WM_INITMENU); Menu::Instance()->Initialize(); break; } case WM_MENUSELECT: { if (HIWORD(wParam) == 0xffff) { HandleMenuEvent(WM_MENUSELECT); } break; } case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: { if (HandleMouseEvent(ScreenInfo, Message, wParam, lParam)) { if (Message != WM_MOUSEWHEEL && Message != WM_MOUSEHWHEEL) { goto CallDefWin; } } else { break; } // Don't handle zoom. if (wParam & MK_CONTROL) { goto CallDefWin; } Status = 1; bool isMouseWheel = Message == WM_MOUSEWHEEL; bool isMouseHWheel = Message == WM_MOUSEHWHEEL; if (isMouseWheel || isMouseHWheel) { short wheelDelta = (short)HIWORD(wParam); bool hasShift = (wParam & MK_SHIFT) ? true : false; Scrolling::s_HandleMouseWheel(isMouseWheel, isMouseHWheel, wheelDelta, hasShift, ScreenInfo); } break; } case CM_SET_WINDOW_SIZE: { Status = _InternalSetWindowSize(); break; } case CM_BEEP: { UnlockConsole(); Unlock = FALSE; // Don't fall back to Beep() on win32 systems -- if the user configures their system for no sound, we should // respect that. PlaySoundW((LPCWSTR)SND_ALIAS_SYSTEMHAND, nullptr, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); break; } case CM_UPDATE_SCROLL_BARS: { ScreenInfo.InternalUpdateScrollBars(); break; } case CM_UPDATE_TITLE: { // SetWindowTextW needs null terminated string so assign view to string. const std::wstring titleAndPrefix{ gci.GetTitleAndPrefix() }; SetWindowTextW(hWnd, titleAndPrefix.c_str()); break; } case CM_UPDATE_EDITKEYS: { // Re-read the edit key settings from registry. Registry reg(&gci); reg.GetEditKeys(nullptr); break; } #ifdef DBG case CM_SET_KEY_STATE: { const int keyboardInputTableStateSize = 256; if (wParam < keyboardInputTableStateSize) { BYTE keyState[keyboardInputTableStateSize]; GetKeyboardState(keyState); keyState[wParam] = static_cast(lParam); SetKeyboardState(keyState); } else { LOG_HR_MSG(E_INVALIDARG, "CM_SET_KEY_STATE invalid wParam"); } break; } case CM_SET_KEYBOARD_LAYOUT: { try { std::wstringstream wss; wss << std::setfill(L'0') << std::setw(8) << wParam; std::wstring wstr(wss.str()); LoadKeyboardLayout(wstr.c_str(), KLF_ACTIVATE); } catch (...) { LOG_HR_MSG(wil::ResultFromCaughtException(), "CM_SET_KEYBOARD_LAYOUT exception"); } break; } #endif DBG case EVENT_CONSOLE_CARET: case EVENT_CONSOLE_UPDATE_REGION: case EVENT_CONSOLE_UPDATE_SIMPLE: case EVENT_CONSOLE_UPDATE_SCROLL: case EVENT_CONSOLE_LAYOUT: case EVENT_CONSOLE_START_APPLICATION: case EVENT_CONSOLE_END_APPLICATION: { NotifyWinEvent(Message, hWnd, (LONG)wParam, (LONG)lParam); break; } default: CallDefWin: { if (Unlock) { UnlockConsole(); Unlock = FALSE; } Status = DefWindowProcW(hWnd, Message, wParam, lParam); break; } } if (Unlock) { UnlockConsole(); } return Status; } #pragma endregion // Helper handler methods for specific cases within the large window procedure are in this section #pragma region Message Handlers void Window::_HandleWindowPosChanged(const LPARAM lParam) { HWND hWnd = GetWindowHandle(); SCREEN_INFORMATION& ScreenInfo = GetScreenInfo(); LPWINDOWPOS const lpWindowPos = (LPWINDOWPOS)lParam; // If the frame changed, update the system metrics. if (WI_IsFlagSet(lpWindowPos->flags, SWP_FRAMECHANGED)) { _UpdateSystemMetrics(); } // This message is sent as the result of someone calling SetWindowPos(). We use it here to set/clear the // CONSOLE_IS_ICONIC bit appropriately. doing so in the WM_SIZE handler is incorrect because the WM_SIZE // comes after the WM_ERASEBKGND during SetWindowPos() processing, and the WM_ERASEBKGND needs to know if // the console window is iconic or not. if (!ScreenInfo.ResizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) { // calculate the dimensions for the newly proposed window rectangle RECT rcNew; s_ConvertWindowPosToWindowRect(lpWindowPos, &rcNew); ServiceLocator::LocateWindowMetrics()->ConvertWindowRectToClientRect(&rcNew); // If the window is not being resized, including a DPI change, then // don't do anything except update our windowrect if (!WI_IsFlagSet(lpWindowPos->flags, SWP_NOSIZE) || _fInDPIChange) { ScreenInfo.ProcessResizeWindow(&rcNew, &_rcClientLast); } // now that operations are complete, save the new rectangle size as the last seen value _rcClientLast = rcNew; } } // Routine Description: // - This helper method for the window procedure will handle the WM_PAINT message // - It will retrieve the invalid rectangle and dispatch that information to the attached renderer // (if available). It will then attempt to validate/finalize the paint to appease the system // and prevent more WM_PAINTs from coming back (until of course something else causes an invalidation). // Arguments: // - // Return Value: // - S_OK if we succeeded. ERROR_INVALID_HANDLE if there is no HWND. E_FAIL if GDI failed for some reason. [[nodiscard]] HRESULT Window::_HandlePaint() const { HWND const hwnd = GetWindowHandle(); RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE), hwnd); // We have to call BeginPaint to retrieve the invalid rectangle state // BeginPaint/EndPaint does a bunch of other magic in the system level // that we can't sufficiently replicate with GetInvalidRect/ValidateRect. // --- // We've tried in the past to not call BeginPaint/EndPaint // and under certain circumstances (windows with SW_HIDE, SKUs without DWM, etc.) // the system either sends WM_PAINT messages ad nauseum or fails to redraw everything correctly. PAINTSTRUCT ps; HDC const hdc = BeginPaint(hwnd, &ps); RETURN_HR_IF_NULL(E_FAIL, hdc); if (ServiceLocator::LocateGlobals().pRender != nullptr) { // In lieu of actually painting right now, we're just going to aggregate this information in the renderer // and let it paint whenever it feels appropriate. RECT const rcUpdate = ps.rcPaint; ServiceLocator::LocateGlobals().pRender->TriggerSystemRedraw(&rcUpdate); } LOG_IF_WIN32_BOOL_FALSE(EndPaint(hwnd, &ps)); return S_OK; } // Routine Description: // - This routine is called when ConsoleWindowProc receives a WM_DROPFILES message. // - It initially calls DragQueryFile() to calculate the number of files dropped and then DragQueryFile() is called to retrieve the filename. // - DoStringPaste() pastes the filename to the console window // Arguments: // - wParam - Identifies the structure containing the filenames of the dropped files. // - Console - Pointer to CONSOLE_INFORMATION structure // Return Value: // - void Window::_HandleDrop(const WPARAM wParam) const { WCHAR szPath[MAX_PATH]; BOOL fAddQuotes; if (DragQueryFile((HDROP)wParam, 0, szPath, ARRAYSIZE(szPath)) != 0) { // Log a telemetry flag saying the user interacted with the Console // Only log when DragQueryFile succeeds, because if we don't when the console starts up, we're seeing // _HandleDrop get called multiple times (and DragQueryFile fail), // which can incorrectly mark this console session as interactive. Telemetry::Instance().SetUserInteractive(); fAddQuotes = (wcschr(szPath, L' ') != nullptr); if (fAddQuotes) { Clipboard::Instance().StringPaste(L"\"", 1); } Clipboard::Instance().StringPaste(szPath, wcslen(szPath)); if (fAddQuotes) { Clipboard::Instance().StringPaste(L"\"", 1); } } } [[nodiscard]] LRESULT Window::_HandleGetObject(const HWND hwnd, const WPARAM wParam, const LPARAM lParam) { LRESULT retVal = 0; // If we are receiving a request from Microsoft UI Automation framework, then return the basic UIA COM interface. if (static_cast(lParam) == static_cast(UiaRootObjectId)) { // NOTE: Deliverable MSFT: 10881045 is required before this will work properly. // The UIAutomationCore.dll cannot currently handle the fact that our HWND is assigned to the child PID. // It will attempt to set up events/pipes on the wrong PID/HWND combination when called here. // A temporary workaround until that is delivered is to disable window handle reparenting using // ConsoleControl's ConsoleSetWindowOwner call. retVal = UiaReturnRawElementProvider(hwnd, wParam, lParam, _GetUiaProvider()); } // Otherwise, return 0. We don't implement MS Active Accessibility (the other framework that calls WM_GETOBJECT). return retVal; } #pragma endregion // Dispatchers are used to post or send a window message into the queue from other portions of the codebase without accessing internal properties directly #pragma region Dispatchers BOOL Window::PostUpdateWindowSize() const { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const SCREEN_INFORMATION& ScreenInfo = GetScreenInfo(); if (ScreenInfo.ConvScreenInfo != nullptr) { return FALSE; } if (gci.Flags & CONSOLE_SETTING_WINDOW_SIZE) { return FALSE; } gci.Flags |= CONSOLE_SETTING_WINDOW_SIZE; return PostMessageW(GetWindowHandle(), CM_SET_WINDOW_SIZE, (WPARAM)&ScreenInfo, 0); } BOOL Window::SendNotifyBeep() const { return SendNotifyMessageW(GetWindowHandle(), CM_BEEP, 0, 0); } BOOL Window::PostUpdateScrollBars() const { return PostMessageW(GetWindowHandle(), CM_UPDATE_SCROLL_BARS, (WPARAM)&GetScreenInfo(), 0); } BOOL Window::PostUpdateExtendedEditKeys() const { return PostMessageW(GetWindowHandle(), CM_UPDATE_EDITKEYS, 0, 0); } #pragma endregion