1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "platform/globals.h" |
6 | #if defined(HOST_OS_WINDOWS) |
7 | #include "bin/console.h" |
8 | |
9 | #include "bin/file.h" |
10 | #include "bin/lockers.h" |
11 | #include "bin/platform.h" |
12 | #include "bin/utils.h" |
13 | #include "bin/utils_win.h" |
14 | |
15 | // These are not always defined in the header files. See: |
16 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx |
17 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING |
18 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 |
19 | #endif |
20 | |
21 | namespace dart { |
22 | namespace bin { |
23 | |
24 | class ConsoleWin { |
25 | public: |
26 | static const int kInvalidFlag = -1; |
27 | |
28 | static void Initialize() { |
29 | saved_output_cp_ = kInvalidFlag; |
30 | saved_input_cp_ = kInvalidFlag; |
31 | // Set up a signal handler that restores the console state on a |
32 | // CTRL_C_EVENT signal. This will only run when there is no signal handler |
33 | // registered for the CTRL_C_EVENT from Dart code. |
34 | SetConsoleCtrlHandler(SignalHandler, TRUE); |
35 | |
36 | // Set both the input and output code pages to UTF8. |
37 | const int output_cp = GetConsoleOutputCP(); |
38 | const int input_cp = GetConsoleCP(); |
39 | if (output_cp != CP_UTF8) { |
40 | SetConsoleOutputCP(CP_UTF8); |
41 | saved_output_cp_ = output_cp; |
42 | } |
43 | if (input_cp != CP_UTF8) { |
44 | SetConsoleCP(CP_UTF8); |
45 | saved_input_cp_ = input_cp; |
46 | } |
47 | |
48 | // Try to set the bits for ANSI support, but swallow any failures. |
49 | saved_stdout_mode_ = |
50 | ModifyMode(STD_OUTPUT_HANDLE, ENABLE_VIRTUAL_TERMINAL_PROCESSING); |
51 | saved_stderr_mode_ = |
52 | ModifyMode(STD_ERROR_HANDLE, ENABLE_VIRTUAL_TERMINAL_PROCESSING); |
53 | saved_stdin_mode_ = ModifyMode(STD_INPUT_HANDLE, 0); |
54 | |
55 | // TODO(28984): Due to issue #29104, we cannot set |
56 | // ENABLE_VIRTUAL_TERMINAL_INPUT here, as it causes ENABLE_PROCESSED_INPUT |
57 | // to be ignored. |
58 | } |
59 | |
60 | static void Cleanup() { |
61 | // STD_OUTPUT_HANDLE, may have been closed or redirected. Therefore, we |
62 | // explicitly open the CONOUT$, CONERR$ and CONIN$ devices, so that we can |
63 | // be sure that we are really restoring the console to its original state. |
64 | if (saved_stdout_mode_ != kInvalidFlag) { |
65 | CleanupDevices("CONOUT$" , STD_OUTPUT_HANDLE, saved_stdout_mode_); |
66 | saved_stdout_mode_ = kInvalidFlag; |
67 | } |
68 | if (saved_stderr_mode_ != kInvalidFlag) { |
69 | CleanupDevices("CONERR$" , STD_ERROR_HANDLE, saved_stderr_mode_); |
70 | } |
71 | if (saved_stdin_mode_ != kInvalidFlag) { |
72 | CleanupDevices("CONIN$" , STD_INPUT_HANDLE, saved_stdin_mode_); |
73 | } |
74 | if (saved_output_cp_ != kInvalidFlag) { |
75 | SetConsoleOutputCP(saved_output_cp_); |
76 | saved_output_cp_ = kInvalidFlag; |
77 | } |
78 | if (saved_input_cp_ != kInvalidFlag) { |
79 | SetConsoleCP(saved_input_cp_); |
80 | saved_input_cp_ = kInvalidFlag; |
81 | } |
82 | } |
83 | |
84 | private: |
85 | static int saved_output_cp_; |
86 | static int saved_input_cp_; |
87 | static DWORD saved_stdout_mode_; |
88 | static DWORD saved_stderr_mode_; |
89 | static DWORD saved_stdin_mode_; |
90 | |
91 | static BOOL WINAPI SignalHandler(DWORD signal) { |
92 | if (signal == CTRL_C_EVENT) { |
93 | Cleanup(); |
94 | } |
95 | return FALSE; |
96 | } |
97 | |
98 | static DWORD ModifyMode(DWORD handle, DWORD flags) { |
99 | HANDLE h = GetStdHandle(handle); |
100 | DWORD mode; |
101 | DWORD old_mode = kInvalidFlag; |
102 | |
103 | /// GetConsoleMode fails if this instance of the VM isn't attached to a |
104 | /// console. In that case, we'll just return kInvalidFlag and won't try |
105 | /// to reset the state when we cleanup. |
106 | if ((h != INVALID_HANDLE_VALUE) && GetConsoleMode(h, &mode)) { |
107 | old_mode = mode; |
108 | if (flags != 0) { |
109 | const DWORD request = mode | flags; |
110 | SetConsoleMode(h, request); |
111 | } |
112 | } |
113 | return old_mode; |
114 | } |
115 | |
116 | static void CleanupDevices(const char* device, |
117 | DWORD handle, |
118 | DWORD orig_flags) { |
119 | const intptr_t kWideBufLen = 64; |
120 | wchar_t widebuf[kWideBufLen]; |
121 | int result = |
122 | MultiByteToWideChar(CP_UTF8, 0, device, -1, widebuf, kWideBufLen); |
123 | ASSERT(result != 0); |
124 | HANDLE h = CreateFileW(widebuf, GENERIC_READ | GENERIC_WRITE, |
125 | FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); |
126 | if (h != INVALID_HANDLE_VALUE) { |
127 | SetStdHandle(STD_OUTPUT_HANDLE, h); |
128 | if (orig_flags != kInvalidFlag) { |
129 | SetConsoleMode(h, orig_flags); |
130 | } |
131 | } |
132 | } |
133 | |
134 | DISALLOW_ALLOCATION(); |
135 | DISALLOW_IMPLICIT_CONSTRUCTORS(ConsoleWin); |
136 | }; |
137 | |
138 | int ConsoleWin::saved_output_cp_ = ConsoleWin::kInvalidFlag; |
139 | int ConsoleWin::saved_input_cp_ = ConsoleWin::kInvalidFlag; |
140 | DWORD ConsoleWin::saved_stdout_mode_ = ConsoleWin::kInvalidFlag; |
141 | DWORD ConsoleWin::saved_stderr_mode_ = ConsoleWin::kInvalidFlag; |
142 | DWORD ConsoleWin::saved_stdin_mode_ = ConsoleWin::kInvalidFlag; |
143 | |
144 | void Console::SaveConfig() { |
145 | ConsoleWin::Initialize(); |
146 | } |
147 | |
148 | void Console::RestoreConfig() { |
149 | ConsoleWin::Cleanup(); |
150 | } |
151 | |
152 | } // namespace bin |
153 | } // namespace dart |
154 | |
155 | #endif // defined(HOST_OS_WINDOWS) |
156 | |