-
Notifications
You must be signed in to change notification settings - Fork 8.5k
/
Copy pathXtermEngine.cpp
464 lines (427 loc) · 17.3 KB
/
XtermEngine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "XtermEngine.hpp"
#include "../../types/inc/convert.hpp"
#pragma hdrstop
using namespace Microsoft::Console;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
const IDefaultColorProvider& colorProvider,
const Viewport initialViewport,
_In_reads_(cColorTable) const COLORREF* const ColorTable,
const WORD cColorTable,
const bool fUseAsciiOnly) :
VtEngine(std::move(hPipe), colorProvider, initialViewport),
_ColorTable(ColorTable),
_cColorTable(cColorTable),
_fUseAsciiOnly(fUseAsciiOnly),
_previousLineWrapped(false),
_usingUnderLine(false),
_needToDisableCursor(false),
_lastCursorIsVisible(false),
_nextCursorIsVisible(true)
{
// Set out initial cursor position to -1, -1. This will force our initial
// paint to manually move the cursor to 0, 0, not just ignore it.
_lastText = VtEngine::INVALID_COORDS;
}
// Method Description:
// - Prepares internal structures for a painting operation. Turns the cursor
// off, so we don't see it flashing all over the client's screen as we
// paint the new contents.
// Arguments:
// - <none>
// Return Value:
// - S_OK if we started to paint. S_FALSE if we didn't need to paint. HRESULT
// error code if painting didn't start successfully, or we failed to write
// the pipe.
[[nodiscard]] HRESULT XtermEngine::StartPaint() noexcept
{
RETURN_IF_FAILED(VtEngine::StartPaint());
_trace.TraceLastText(_lastText);
// Prep us to think that the cursor is not visible this frame. If it _is_
// visible, then PaintCursor will be called, and we'll set this to true
// during the frame.
_nextCursorIsVisible = false;
if (_firstPaint)
{
// MSFT:17815688
// If the caller requested to inherit the cursor, we shouldn't
// clear the screen on the first paint. Otherwise, we'll clear
// the screen on the first paint, just to make sure that the
// terminal's state is consistent with what we'll be rendering.
RETURN_IF_FAILED(_ClearScreen());
_clearedAllThisFrame = true;
_firstPaint = false;
}
else
{
const auto dirtyRect = GetDirtyRectInChars();
const auto dirtyView = Viewport::FromInclusive(dirtyRect);
if (!_resized && dirtyView == _lastViewport)
{
// TODO: MSFT:21096414 - This is never actually hit. We set
// _resized=true on every frame (see VtEngine::UpdateViewport).
// Unfortunately, not always setting _resized is not a good enough
// solution, see that work item for a description why.
RETURN_IF_FAILED(_ClearScreen());
_clearedAllThisFrame = true;
}
}
if (!_quickReturn)
{
if (_WillWriteSingleChar())
{
// Don't re-enable the cursor.
_quickReturn = true;
}
}
return S_OK;
}
// Routine Description:
// - EndPaint helper to perform the final rendering steps. Turn the cursor back
// on.
// Arguments:
// - <none>
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT XtermEngine::EndPaint() noexcept
{
// If during the frame we determined that the cursor needed to be disabled,
// then insert a cursor off at the start of the buffer, and re-enable
// the cursor here.
if (_needToDisableCursor)
{
// If the cursor was previously visible, let's hide it for this frame,
// by prepending a cursor off.
if (_lastCursorIsVisible)
{
_buffer.insert(0, "\x1b[25l");
_lastCursorIsVisible = false;
}
// If the cursor was NOT previously visible, then that's fine! we don't
// need to worry, it's already off.
}
// If the cursor is moving from off -> on (including cases where we just
// disabled if for this frame), show the cursor at the end of the frame
if (_nextCursorIsVisible && !_lastCursorIsVisible)
{
RETURN_IF_FAILED(_ShowCursor());
}
// Otherwise, if the cursor previously was visible, and it should be hidden
// (on -> off), hide it at the end of the frame.
else if (!_nextCursorIsVisible && _lastCursorIsVisible)
{
RETURN_IF_FAILED(_HideCursor());
}
// Update our tracker of what we thought the last cursor state of the
// terminal was.
_lastCursorIsVisible = _nextCursorIsVisible;
RETURN_IF_FAILED(VtEngine::EndPaint());
_needToDisableCursor = false;
return S_OK;
}
// Routine Description:
// - Write a VT sequence to either start or stop underlining text.
// Arguments:
// - legacyColorAttribute: A console attributes bit field containing information
// about the underlining state of the text.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT XtermEngine::_UpdateUnderline(const WORD legacyColorAttribute) noexcept
{
bool textUnderlined = WI_IsFlagSet(legacyColorAttribute, COMMON_LVB_UNDERSCORE);
if (textUnderlined != _usingUnderLine)
{
if (textUnderlined)
{
RETURN_IF_FAILED(_BeginUnderline());
}
else
{
RETURN_IF_FAILED(_EndUnderline());
}
_usingUnderLine = textUnderlined;
}
return S_OK;
}
// Routine Description:
// - Write a VT sequence to change the current colors of text. Only writes
// 16-color attributes.
// Arguments:
// - colorForeground: The RGB Color to use to paint the foreground text.
// - colorBackground: The RGB Color to use to paint the background of the text.
// - legacyColorAttribute: A console attributes bit field specifying the brush
// colors we should use.
// - extendedAttrs - extended text attributes (italic, underline, etc.) to use.
// - isSettingDefaultBrushes: indicates if we should change the background color of
// the window. Unused for VT
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT XtermEngine::UpdateDrawingBrushes(const COLORREF colorForeground,
const COLORREF colorBackground,
const WORD legacyColorAttribute,
const ExtendedAttributes extendedAttrs,
const bool /*isSettingDefaultBrushes*/) noexcept
{
//When we update the brushes, check the wAttrs to see if the LVB_UNDERSCORE
// flag is there. If the state of that flag is different then our
// current state, change the underlining state.
// We have to do this here, instead of in PaintBufferGridLines, because
// we'll have already painted the text by the time PaintBufferGridLines
// is called.
// TODO:GH#2915 Treat underline separately from LVB_UNDERSCORE
RETURN_IF_FAILED(_UpdateUnderline(legacyColorAttribute));
// The base xterm mode only knows about 16 colors
return VtEngine::_16ColorUpdateDrawingBrushes(colorForeground,
colorBackground,
WI_IsFlagSet(extendedAttrs, ExtendedAttributes::Bold),
_ColorTable,
_cColorTable);
}
// Routine Description:
// - Draws the cursor on the screen
// Arguments:
// - options - Options that affect the presentation of the cursor
// Return Value:
// - S_OK or suitable HRESULT error from writing pipe.
[[nodiscard]] HRESULT XtermEngine::PaintCursor(const IRenderEngine::CursorOptions& options) noexcept
{
// PaintCursor is only called when the cursor is in fact visible in a single
// frame. When this is called, mark _nextCursorIsVisible as true. At the end
// of the frame, we'll decide to either turn the cursor on or not, based
// upon the previous state.
// When this method is not called during a frame, it's because the cursor
// was not visible. In that case, at the end of the frame,
// _nextCursorIsVisible will still be false (from when we set it during
// StartPaint)
_nextCursorIsVisible = true;
return VtEngine::PaintCursor(options);
}
// Routine Description:
// - Write a VT sequence to move the cursor to the specified coordinates. We
// also store the last place we left the cursor for future optimizations.
// If the cursor only needs to go to the origin, only write the home sequence.
// If the new cursor is only down one line from the current, only write a newline
// If the new cursor is only down one line and at the start of the line, write
// a carriage return.
// Otherwise just write the whole sequence for moving it.
// Arguments:
// - coord: location to move the cursor to.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT XtermEngine::_MoveCursor(COORD const coord) noexcept
{
HRESULT hr = S_OK;
if (coord.X != _lastText.X || coord.Y != _lastText.Y)
{
if (coord.X == 0 && coord.Y == 0)
{
_needToDisableCursor = true;
hr = _CursorHome();
}
else if (coord.X == 0 && coord.Y == (_lastText.Y + 1))
{
// Down one line, at the start of the line.
// If the previous line wrapped, then the cursor is already at this
// position, we just don't know it yet. Don't emit anything.
if (_previousLineWrapped)
{
hr = S_OK;
}
else
{
std::string seq = "\r\n";
hr = _Write(seq);
}
}
else if (coord.X == 0 && coord.Y == _lastText.Y)
{
// Start of this line
std::string seq = "\r";
hr = _Write(seq);
}
else if (coord.X == _lastText.X && coord.Y == (_lastText.Y + 1))
{
// Down one line, same X position
std::string seq = "\n";
hr = _Write(seq);
}
else if (coord.X == (_lastText.X - 1) && coord.Y == (_lastText.Y))
{
// Back one char, same Y position
std::string seq = "\b";
hr = _Write(seq);
}
else if (coord.Y == _lastText.Y && coord.X > _lastText.X)
{
// Same line, forward some distance
short distance = coord.X - _lastText.X;
hr = _CursorForward(distance);
}
else
{
_needToDisableCursor = true;
hr = _CursorPosition(coord);
}
if (SUCCEEDED(hr))
{
_lastText = coord;
}
}
if (_lastText.Y != _lastViewport.ToOrigin().BottomInclusive())
{
_newBottomLine = false;
}
_deferredCursorPos = INVALID_COORDS;
return hr;
}
// Routine Description:
// - Scrolls the existing data on the in-memory frame by the scroll region
// deltas we have collectively received through the Invalidate methods
// since the last time this was called.
// Move the cursor to the origin, and insert or delete rows as appropriate.
// The inserted rows will be blank, but marked invalid by InvalidateScroll,
// so they will later be written by PaintBufferLine.
// Arguments:
// - <none>
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT XtermEngine::ScrollFrame() noexcept
{
if (_scrollDelta.X != 0)
{
// No easy way to shift left-right. Everything needs repainting.
return InvalidateAll();
}
if (_scrollDelta.Y == 0)
{
// There's nothing to do here. Do nothing.
return S_OK;
}
const short dy = _scrollDelta.Y;
const short absDy = static_cast<short>(abs(dy));
HRESULT hr = S_OK;
if (dy < 0)
{
// Instead of deleting the first line (causing everything to move up)
// move to the bottom of the buffer, and newline.
// That will cause everything to move up, by moving the viewport down.
// This will let remote conhosts scroll up to see history like normal.
const short bottom = _lastViewport.ToOrigin().BottomInclusive();
hr = _MoveCursor({ 0, bottom });
if (SUCCEEDED(hr))
{
std::string seq = std::string(absDy, '\n');
hr = _Write(seq);
// Mark that the bottom line is new, so we won't spend time with an
// ECH on it.
_newBottomLine = true;
}
// We don't need to _MoveCursor the cursor again, because it's still
// at the bottom of the viewport.
}
else if (dy > 0)
{
// Move to the top of the buffer, and insert some lines of text, to
// cause the viewport contents to shift down.
hr = _MoveCursor({ 0, 0 });
if (SUCCEEDED(hr))
{
hr = _InsertLine(absDy);
}
}
return hr;
}
// Routine Description:
// - Notifies us that the console is attempting to scroll the existing screen
// area. Add the top or bottom rows to the invalid region, and update the
// total scroll delta accumulated this frame.
// Arguments:
// - pcoordDelta - Pointer to character dimension (COORD) of the distance the
// console would like us to move while scrolling.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for safemath failure
[[nodiscard]] HRESULT XtermEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
{
const short dx = pcoordDelta->X;
const short dy = pcoordDelta->Y;
if (dx != 0 || dy != 0)
{
// Scroll the current offset
RETURN_IF_FAILED(_InvalidOffset(pcoordDelta));
// Add the top/bottom of the window to the invalid area
SMALL_RECT invalid = _lastViewport.ToOrigin().ToExclusive();
if (dy > 0)
{
invalid.Bottom = dy;
}
else if (dy < 0)
{
invalid.Top = invalid.Bottom + dy;
}
LOG_IF_FAILED(_InvalidCombine(Viewport::FromExclusive(invalid)));
COORD invalidScrollNew;
RETURN_IF_FAILED(ShortAdd(_scrollDelta.X, dx, &invalidScrollNew.X));
RETURN_IF_FAILED(ShortAdd(_scrollDelta.Y, dy, &invalidScrollNew.Y));
// Store if safemath succeeded
_scrollDelta = invalidScrollNew;
}
return S_OK;
}
// Routine Description:
// - Draws one line of the buffer to the screen. Writes the characters to the
// pipe, encoded in UTF-8 or ASCII only, depending on the VtIoMode.
// (See descriptions of both implementations for details.)
// Arguments:
// - clusters - text and column counts for each piece of text.
// - coord - character coordinate target to render within viewport
// - trimLeft - This specifies whether to trim one character width off the left
// side of the output. Used for drawing the right-half only of a
// double-wide character.
// Return Value:
// - S_OK or suitable HRESULT error from writing pipe.
[[nodiscard]] HRESULT XtermEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool /*trimLeft*/) noexcept
{
return _fUseAsciiOnly ?
VtEngine::_PaintAsciiBufferLine(clusters, coord) :
VtEngine::_PaintUtf8BufferLine(clusters, coord);
}
// Method Description:
// - Wrapper for ITerminalOutputConnection. Write either an ascii-only, or a
// proper utf-8 string, depending on our mode.
// Arguments:
// - wstr - wstring of text to be written
// Return Value:
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
[[nodiscard]] HRESULT XtermEngine::WriteTerminalW(const std::wstring_view wstr) noexcept
{
return _fUseAsciiOnly ?
VtEngine::_WriteTerminalAscii(wstr) :
VtEngine::_WriteTerminalUtf8(wstr);
}
// Method Description:
// - Updates the window's title string. Emits the VT sequence to SetWindowTitle.
// Arguments:
// - newTitle: the new string to use for the title of the window
// Return Value:
// - S_OK
[[nodiscard]] HRESULT XtermEngine::_DoUpdateTitle(const std::wstring& newTitle) noexcept
{
// inbox telnet uses xterm-ascii as its mode. If we're in ascii mode, don't
// do anything, to maintain compatibility.
if (_fUseAsciiOnly)
{
return S_OK;
}
try
{
const auto converted = ConvertToA(CP_UTF8, newTitle);
return VtEngine::_ChangeTitle(converted);
}
CATCH_RETURN();
}