| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | ConsoleState.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains function definitions for the ConsoleState helper class. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "svccomm.hpp" |
| 17 | #include "ConsoleState.h" |
| 18 | #pragma hdrstop |
| 19 | |
| 20 | namespace { |
| 21 | |
| 22 | void ChangeConsoleMode(_In_ HANDLE Handle, _In_ DWORD Mode) |
| 23 | { |
| 24 | // Use the invalid parameter error code to detect the v1 console that does not support the provided mode. |
| 25 | // This can be improved in the future when a more elegant solution exists. |
| 26 | // |
| 27 | // N.B. Ignore failures setting the mode if the console has already disconnected. |
| 28 | if (!SetConsoleMode(Handle, Mode)) |
| 29 | { |
| 30 | // DISABLE_NEWLINE_AUTO_RETURN is not supported everywhere, if the flag was present fall back and try again. |
| 31 | if (WI_IsFlagSet(Mode, DISABLE_NEWLINE_AUTO_RETURN)) |
| 32 | { |
| 33 | Mode = WI_ClearFlag(Mode, DISABLE_NEWLINE_AUTO_RETURN); |
| 34 | if (SetConsoleMode(Handle, Mode)) |
| 35 | { |
| 36 | return; |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | switch (GetLastError()) |
| 41 | { |
| 42 | case ERROR_PIPE_NOT_CONNECTED: |
| 43 | break; |
| 44 | |
| 45 | case ERROR_INVALID_PARAMETER: |
| 46 | THROW_HR_MSG(WSL_E_CONSOLE, "SetConsoleMode(0x%x) failed", Mode); |
| 47 | |
| 48 | default: |
| 49 | THROW_LAST_ERROR_MSG("SetConsoleMode(0x%x) failed", Mode); |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | void TrySetConsoleMode(_In_ HANDLE Handle, _In_ DWORD Mode) |
| 55 | try |
| 56 | { |
| 57 | ChangeConsoleMode(Handle, Mode); |
| 58 | } |
| 59 | CATCH_LOG() |
| 60 | |
| 61 | } // namespace |
| 62 | |
| 63 | namespace wsl::windows::common { |
| 64 | |
| 65 | ConsoleState::ConsoleState() |
| 66 | { |
| 67 | m_InputHandle.reset( |
| 68 | CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr)); |
| 69 | |
| 70 | if (!m_InputHandle) |
| 71 | { |
| 72 | LOG_LAST_ERROR_MSG("CreateFileW(CONIN$) failed"); |
| 73 | } |
| 74 | |
| 75 | m_OutputHandle.reset( |
| 76 | CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr)); |
| 77 | |
| 78 | if (!m_OutputHandle) |
| 79 | { |
| 80 | LOG_LAST_ERROR_MSG("CreateFileW(CONOUT$) failed"); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | void ConsoleState::SetInteractiveMode() |
| 85 | { |
| 86 | if (m_interactiveModeConfigured) |
| 87 | { |
| 88 | return; |
| 89 | } |
| 90 | |
| 91 | // Ensure console state is restored if this method throws. |
| 92 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { RestoreConsoleState(); }); |
| 93 | |
| 94 | if (m_InputHandle) |
| 95 | { |
| 96 | m_SavedInputCodePage = GetConsoleCP(); |
| 97 | LOG_IF_WIN32_BOOL_FALSE(SetConsoleCP(CP_UTF8)); |
| 98 | |
| 99 | // Configure for raw input with VT support. |
| 100 | DWORD mode; |
| 101 | THROW_LAST_ERROR_IF(!GetConsoleMode(m_InputHandle.get(), &mode)); |
| 102 | |
| 103 | DWORD NewMode = mode; |
| 104 | WI_SetAllFlags(NewMode, ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT); |
| 105 | WI_ClearAllFlags(NewMode, ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); |
| 106 | ChangeConsoleMode(m_InputHandle.get(), NewMode); |
| 107 | m_SavedInputMode = mode; |
| 108 | } |
| 109 | |
| 110 | if (m_OutputHandle) |
| 111 | { |
| 112 | if (!m_SavedOutputCodePage.has_value()) |
| 113 | { |
| 114 | m_SavedOutputCodePage = GetConsoleOutputCP(); |
| 115 | } |
| 116 | |
| 117 | LOG_IF_WIN32_BOOL_FALSE(SetConsoleOutputCP(CP_UTF8)); |
| 118 | |
| 119 | // Configure for VT output. |
| 120 | DWORD mode; |
| 121 | THROW_LAST_ERROR_IF(!GetConsoleMode(m_OutputHandle.get(), &mode)); |
| 122 | |
| 123 | DWORD NewMode = mode; |
| 124 | WI_SetAllFlags(NewMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN); |
| 125 | ChangeConsoleMode(m_OutputHandle.get(), NewMode); |
| 126 | m_SavedOutputMode = mode; |
| 127 | } |
| 128 | |
| 129 | m_interactiveModeConfigured = true; |
| 130 | cleanup.release(); |
| 131 | } |
| 132 | |
| 133 | void ConsoleState::SetOutputCodePageUtf8() |
| 134 | { |
| 135 | if (!m_OutputHandle) |
| 136 | { |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | if (!m_SavedOutputCodePage.has_value()) |
| 141 | { |
| 142 | m_SavedOutputCodePage = GetConsoleOutputCP(); |
| 143 | } |
| 144 | |
| 145 | LOG_IF_WIN32_BOOL_FALSE(SetConsoleOutputCP(CP_UTF8)); |
| 146 | } |
| 147 | |
| 148 | ConsoleState::~ConsoleState() |
| 149 | { |
| 150 | RestoreConsoleState(); |
| 151 | } |
| 152 | |
| 153 | void ConsoleState::RestoreConsoleState() |
| 154 | { |
| 155 | if (m_InputHandle) |
| 156 | { |
| 157 | if (m_SavedInputCodePage.has_value()) |
| 158 | { |
| 159 | LOG_IF_WIN32_BOOL_FALSE(SetConsoleCP(m_SavedInputCodePage.value())); |
| 160 | m_SavedInputCodePage.reset(); |
| 161 | } |
| 162 | |
| 163 | if (m_SavedInputMode.has_value()) |
| 164 | { |
| 165 | TrySetConsoleMode(m_InputHandle.get(), m_SavedInputMode.value()); |
| 166 | m_SavedInputMode.reset(); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | if (m_OutputHandle) |
| 171 | { |
| 172 | if (m_SavedOutputCodePage.has_value()) |
| 173 | { |
| 174 | LOG_IF_WIN32_BOOL_FALSE(SetConsoleOutputCP(m_SavedOutputCodePage.value())); |
| 175 | m_SavedOutputCodePage.reset(); |
| 176 | } |
| 177 | |
| 178 | if (m_SavedOutputMode.has_value()) |
| 179 | { |
| 180 | TrySetConsoleMode(m_OutputHandle.get(), m_SavedOutputMode.value()); |
| 181 | m_SavedOutputMode.reset(); |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | COORD ConsoleState::GetWindowSize() const |
| 187 | { |
| 188 | if (m_OutputHandle) |
| 189 | { |
| 190 | CONSOLE_SCREEN_BUFFER_INFOEX Info{}; |
| 191 | Info.cbSize = sizeof(Info); |
| 192 | THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(m_OutputHandle.get(), &Info)); |
| 193 | return { |
| 194 | static_cast<short>(Info.srWindow.Right - Info.srWindow.Left + 1), |
| 195 | static_cast<short>(Info.srWindow.Bottom - Info.srWindow.Top + 1)}; |
| 196 | } |
| 197 | |
| 198 | LOG_HR_MSG(E_UNEXPECTED, "No console handle available for GetWindowSize"); |
| 199 | return {80, 24}; |
| 200 | } |
| 201 | |
| 202 | } // namespace wsl::windows::common |