1 // Copyright (c) 2011-2015 Ryan Prichard
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
29 #include "NamedPipe.h"
30 #include "UnicodeEncoding.h"
31 #include "../shared/DebugClient.h"
32 #include "../shared/WinptyAssert.h"
33 #include "../shared/winpty_snprintf.h"
37 // Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and
38 // COMMON_LVB_TRAILING_BYTE.
39 const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100;
40 const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200;
41 const int WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000;
42 const int WINPTY_COMMON_LVB_UNDERSCORE = 0x8000;
44 const int COLOR_ATTRIBUTE_MASK =
48 FOREGROUND_INTENSITY |
52 BACKGROUND_INTENSITY |
53 WINPTY_COMMON_LVB_REVERSE_VIDEO |
54 WINPTY_COMMON_LVB_UNDERSCORE;
56 const int FLAG_RED = 1;
57 const int FLAG_GREEN = 2;
58 const int FLAG_BLUE = 4;
59 const int FLAG_BRIGHT = 8;
62 const int DKGRAY = BLACK | FLAG_BRIGHT;
63 const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE;
64 const int WHITE = LTGRAY | FLAG_BRIGHT;
66 // SGR parameters (Select Graphic Rendition)
67 const int SGR_FORE = 30;
68 const int SGR_FORE_HI = 90;
69 const int SGR_BACK = 40;
70 const int SGR_BACK_HI = 100;
74 static void outUInt(std::string &out, unsigned int n)
77 char *pbuf = &buf[32];
80 *(--pbuf) = '0' + n % 10;
86 static void outputSetColorSgrParams(std::string &out, bool isFore, int color)
89 const int sgrBase = isFore ? SGR_FORE : SGR_BACK;
90 if (color & FLAG_BRIGHT) {
91 // Some terminals don't support the 9X/10X "intensive" color parameters
92 // (e.g. the Eclipse TM terminal as of this writing). Those terminals
93 // will quietly ignore a 9X/10X code, and the other terminals will
94 // ignore a 3X/4X code if it's followed by a 9X/10X code. Therefore,
95 // output a 3X/4X code as a fallback, then override it.
96 const int colorBase = color & ~FLAG_BRIGHT;
97 outUInt(out, sgrBase + colorBase);
99 outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase);
101 outUInt(out, sgrBase + color);
105 static void outputSetColor(std::string &out, int color)
109 if (color & FOREGROUND_RED) fore |= FLAG_RED;
110 if (color & FOREGROUND_GREEN) fore |= FLAG_GREEN;
111 if (color & FOREGROUND_BLUE) fore |= FLAG_BLUE;
112 if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT;
113 if (color & BACKGROUND_RED) back |= FLAG_RED;
114 if (color & BACKGROUND_GREEN) back |= FLAG_GREEN;
115 if (color & BACKGROUND_BLUE) back |= FLAG_BLUE;
116 if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT;
118 if (color & WINPTY_COMMON_LVB_REVERSE_VIDEO) {
119 // n.b.: The COMMON_LVB_REVERSE_VIDEO flag also swaps
120 // FOREGROUND_INTENSITY and BACKGROUND_INTENSITY. Tested on
121 // Windows 10 v14393.
122 std::swap(fore, back);
125 // Translate the fore/back colors into terminal escape codes using
126 // a heuristic that works OK with common white-on-black or
127 // black-on-white color schemes. We don't know which color scheme
128 // the terminal is using. It is ugly to force white-on-black text
129 // on a black-on-white terminal, and it's even ugly to force the
130 // matching scheme. It's probably relevant that the default
131 // fore/back terminal colors frequently do not match any of the 16
134 // Typical default terminal color schemes (according to palette,
136 // - mintty: LtGray-on-Black(A)
137 // - putty: LtGray-on-Black(A)
138 // - xterm: LtGray-on-Black(A)
139 // - Konsole: LtGray-on-Black(A)
140 // - JediTerm/JetBrains: Black-on-White(B)
141 // - rxvt: Black-on-White(B)
143 // If the background is the default color (black), then it will
144 // map to Black(A) or White(B). If we translate White to White,
145 // then a Black background and a White background in the console
146 // are both White with (B). Therefore, we should translate White
147 // using SGR 7 (Invert). The typical finished mapping table for
148 // background grayscale colors is:
150 // (A) White => LtGray(fore)
151 // (A) Black => Black(back)
152 // (A) LtGray => LtGray
153 // (A) DkGray => DkGray
155 // (B) White => Black(fore)
156 // (B) Black => White(back)
157 // (B) LtGray => LtGray
158 // (B) DkGray => DkGray
163 if (fore == LTGRAY) {
164 // The "default" foreground color. Use the terminal's
166 } else if (fore == WHITE) {
167 // Sending the literal color white would behave poorly if
168 // the terminal were black-on-white. Sending Bold is not
169 // guaranteed to alter the color, but it will make the text
170 // visually distinct, so do that instead.
172 } else if (fore == DKGRAY) {
173 // Set the foreground color to DkGray(90) with a fallback
174 // of LtGray(37) for terminals that don't handle the 9X SGR
175 // parameters (e.g. Eclipse's TM Terminal as of this
177 out.append(";37;90");
179 outputSetColorSgrParams(out, true, fore);
181 } else if (back == WHITE) {
182 // Set the background color using Invert on the default
183 // foreground color, and set the foreground color by setting a
186 // Use the terminal's inverted colors.
188 if (fore == LTGRAY || fore == BLACK) {
189 // We're likely mapping Console White to terminal LtGray or
190 // Black. If they are the Console foreground color, then
191 // don't set a terminal foreground color to avoid creating
194 outputSetColorSgrParams(out, false, fore);
197 // Set the foreground and background to match exactly that in
198 // the Windows console.
199 outputSetColorSgrParams(out, true, fore);
200 outputSetColorSgrParams(out, false, back);
203 // The foreground and background colors are exactly equal, so
204 // attempt to hide the text using the Conceal SGR parameter,
205 // which some terminals support.
208 if (color & WINPTY_COMMON_LVB_UNDERSCORE) {
214 static inline unsigned int fixSpecialCharacters(unsigned int ch)
218 // The Windows Console has a popup window (e.g. that appears with
219 // F7) that is sometimes bordered with box-drawing characters.
220 // With the Japanese and Korean system locales (CP932 and CP949),
221 // the UnicodeChar values for the box-drawing characters are 1
222 // through 6. Detect this and map the values to the correct
225 // N.B. In the English locale, the UnicodeChar values are correct,
226 // and they identify single-line characters rather than
227 // double-line. In the Chinese Simplified and Traditional locales,
228 // the popups use ASCII characters instead.
229 case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT
230 case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT
231 case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT
232 case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT
233 case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL
234 case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL
236 // Convert an escape character to some other character. This
237 // conversion only applies to console cells containing an escape
238 // character. In newer versions of Windows 10 (e.g. 10.0.10586),
239 // the non-legacy console recognizes escape sequences in
240 // WriteConsole and interprets them without writing them to the
241 // cells of the screen buffer. In that case, the conversion here
243 case 0x1b: return '?';
249 static inline bool isFullWidthCharacter(const CHAR_INFO *data, int width)
255 (data[0].Attributes & WINPTY_COMMON_LVB_LEADING_BYTE) &&
256 (data[1].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) &&
257 data[0].Char.UnicodeChar == data[1].Char.UnicodeChar;
260 // Scan to find a single Unicode Scalar Value. Full-width characters occupy
261 // two console cells, and this code also tries to handle UTF-16 surrogate
264 // Windows expands at least some wide characters outside the Basic
265 // Multilingual Plane into four cells, such as U+20000:
266 // 1. 0xD840, attr=0x107
267 // 2. 0xD840, attr=0x207
268 // 3. 0xDC00, attr=0x107
269 // 4. 0xDC00, attr=0x207
270 // Even in the Traditional Chinese locale on Windows 10, this text is rendered
271 // as two boxes, but if those boxes are copied-and-pasted, the character is
273 static inline void scanUnicodeScalarValue(
274 const CHAR_INFO *data, int width,
275 int &outCellCount, unsigned int &outCharValue)
279 const int w1 = isFullWidthCharacter(data, width) ? 2 : 1;
280 const wchar_t c1 = data[0].Char.UnicodeChar;
282 if ((c1 & 0xF800) == 0xD800) {
283 // The first cell is either a leading or trailing surrogate pair.
284 if ((c1 & 0xFC00) != 0xD800 ||
286 ((data[w1].Char.UnicodeChar & 0xFC00) != 0xDC00)) {
287 // Invalid surrogate pair
291 // Valid surrogate pair
292 outCellCount = w1 + (isFullWidthCharacter(&data[w1], width - w1) ? 2 : 1);
293 outCharValue = decodeSurrogatePair(c1, data[w1].Char.UnicodeChar);
301 } // anonymous namespace
303 void Terminal::reset(SendClearFlag sendClearFirst, int64_t newLine)
305 if (sendClearFirst == SendClear && !m_plainMode) {
306 // 0m ==> reset SGR parameters
307 // 1;1H ==> move cursor to top-left position
308 // 2J ==> clear the entire screen
309 m_output.write(CSI "0m" CSI "1;1H" CSI "2J");
311 m_remoteLine = newLine;
314 m_cursorHidden = false;
318 void Terminal::sendLine(int64_t line, const CHAR_INFO *lineData, int width,
323 moveTerminalToLine(line);
325 // If possible, see if we can append to what we've already output for this
327 if (m_lineDataValid) {
328 ASSERT(m_lineData.size() == static_cast<size_t>(m_remoteColumn));
329 if (m_remoteColumn > 0) {
330 // In normal mode, if m_lineData.size() equals `width`, then we
331 // will have trouble outputing the "erase rest of line" command,
332 // which must be output before reaching the end of the line. In
333 // plain mode, we don't output that command, so we're OK with a
335 bool okWidth = false;
337 okWidth = static_cast<size_t>(width) >= m_lineData.size();
339 okWidth = static_cast<size_t>(width) > m_lineData.size();
342 memcmp(m_lineData.data(), lineData,
343 sizeof(CHAR_INFO) * m_lineData.size()) != 0) {
344 m_lineDataValid = false;
348 if (!m_lineDataValid) {
349 // We can't reuse, so we must reset this line.
350 hideTerminalCursor();
352 // We can't backtrack, so repeat this line.
353 m_output.write("\r\n");
355 m_output.write("\r");
357 m_lineDataValid = true;
362 std::string &termLine = m_termLineWorkingBuffer;
364 size_t trimmedLineLength = 0;
365 int trimmedCellCount = m_lineData.size();
366 bool alreadyErasedLine = false;
369 for (int i = m_lineData.size(); i < width; i += cellCount) {
371 int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK;
372 if (color != m_remoteColor) {
373 outputSetColor(termLine, color);
374 trimmedLineLength = termLine.size();
375 m_remoteColor = color;
377 // All the cells just up to this color change will be output.
378 trimmedCellCount = i;
382 scanUnicodeScalarValue(&lineData[i], width - i, cellCount, ch);
384 // Tentatively add this space character. We'll only output it if
385 // we see something interesting after it.
386 termLine.push_back(' ');
388 if (i + cellCount == width) {
389 // We'd like to erase the line after outputting all non-blank
390 // characters, but this doesn't work if the last cell in the
391 // line is non-blank. At the point, the cursor is positioned
392 // just past the end of the line, but in many terminals,
393 // issuing a CSI 0K at that point also erases the last cell in
394 // the line. Work around this behavior by issuing the erase
395 // one character early in that case.
397 termLine.append(CSI "0K"); // Erase from cursor to EOL
399 alreadyErasedLine = true;
401 ch = fixSpecialCharacters(ch);
403 int enclen = encodeUtf8(enc, ch);
408 termLine.append(enc, enclen);
409 trimmedLineLength = termLine.size();
411 // All the cells up to and including this cell will be output.
412 trimmedCellCount = i + cellCount;
416 if (cursorColumn != -1 && trimmedCellCount > cursorColumn) {
417 // The line content would run past the cursor, so hide it before we
419 hideTerminalCursor();
422 m_output.write(termLine.data(), trimmedLineLength);
423 if (!alreadyErasedLine && !m_plainMode) {
424 m_output.write(CSI "0K"); // Erase from cursor to EOL
427 ASSERT(trimmedCellCount <= width);
428 m_lineData.insert(m_lineData.end(),
429 &lineData[m_lineData.size()],
430 &lineData[trimmedCellCount]);
431 m_remoteColumn = trimmedCellCount;
434 void Terminal::showTerminalCursor(int column, int64_t line)
436 moveTerminalToLine(line);
438 if (m_remoteColumn != column) {
440 winpty_snprintf(buffer, CSI "%dG", column + 1);
441 m_output.write(buffer);
442 m_lineDataValid = (column == 0);
444 m_remoteColumn = column;
446 if (m_cursorHidden) {
447 m_output.write(CSI "?25h");
448 m_cursorHidden = false;
453 void Terminal::hideTerminalCursor()
456 if (m_cursorHidden) {
459 m_output.write(CSI "?25l");
460 m_cursorHidden = true;
464 void Terminal::moveTerminalToLine(int64_t line)
466 if (line == m_remoteLine) {
470 // Do not use CPL or CNL. Konsole 2.5.4 does not support Cursor Previous
471 // Line (CPL) -- there are "Undecodable sequence" errors. gnome-terminal
472 // 2.32.0 does handle it. Cursor Next Line (CNL) does nothing if the
473 // cursor is on the last line already.
475 hideTerminalCursor();
477 if (line < m_remoteLine) {
479 // We can't backtrack, so instead repeat the lines again.
480 m_output.write("\r\n");
483 // Backtrack and overwrite previous lines.
486 winpty_snprintf(buffer, "\r" CSI "%uA",
487 static_cast<unsigned int>(m_remoteLine - line));
488 m_output.write(buffer);
491 } else if (line > m_remoteLine) {
492 while (line > m_remoteLine) {
493 m_output.write("\r\n");
498 m_lineDataValid = true;
503 void Terminal::enableMouseMode(bool enabled)
505 if (m_mouseModeEnabled == enabled || m_plainMode) {
508 m_mouseModeEnabled = enabled;
510 // Start by disabling UTF-8 coordinate mode (1005), just in case we
511 // have a terminal that does not support 1006/1015 modes, and 1005
512 // happens to be enabled. The UTF-8 coordinates can't be unambiguously
515 // Enable basic mouse support first (1000), then try to switch to
516 // button-move mode (1002), then try full mouse-move mode (1003).
517 // Terminals that don't support a mode will be stuck at the highest
518 // mode they do support.
520 // Enable encoding mode 1015 first, then try to switch to 1006. On
521 // some terminals, both modes will be enabled, but 1006 will have
522 // priority. On other terminals, 1006 wins because it's listed last.
524 // See misc/MouseInputNotes.txt for details.
527 CSI "?1000h" CSI "?1002h" CSI "?1003h" CSI "?1015h" CSI "?1006h");
529 // Resetting both encoding modes (1006 and 1015) is necessary, but
530 // apparently we only need to use reset on one of the 100[023] modes.
531 // Doing both doesn't hurt.
533 CSI "?1006l" CSI "?1015l" CSI "?1003l" CSI "?1002l" CSI "?1000l");