-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Expand file tree
/
Copy path_stream.cpp
More file actions
693 lines (601 loc) · 27 KB
/
Copy path_stream.cpp
File metadata and controls
693 lines (601 loc) · 27 KB
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
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "ApiRoutines.h"
#include "_stream.h"
#include "stream.h"
#include "writeData.hpp"
#include "_output.h"
#include "output.h"
#include "dbcs.h"
#include "handle.h"
#include "misc.h"
#include "VtIo.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/Viewport.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::VirtualTerminal;
using Microsoft::Console::Interactivity::ServiceLocator;
constexpr bool controlCharPredicate(wchar_t wch)
{
return wch < L' ' || wch == 0x007F;
}
static auto raiseAccessibilityEventsOnExit(SCREEN_INFORMATION& screenInfo)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto bufferBefore = &gci.GetActiveOutputBuffer();
const auto cursorBefore = bufferBefore->GetTextBuffer().GetCursor().GetPosition();
auto raise = wil::scope_exit([bufferBefore, cursorBefore] {
// !!! NOTE !!! `bufferBefore` may now be a stale pointer, because VT
// sequences can switch between the main and alternative screen buffer.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferAfter = &gci.GetActiveOutputBuffer();
const auto cursorAfter = bufferAfter->GetTextBuffer().GetCursor().GetPosition();
if (cursorBefore != cursorAfter)
{
if (bufferBefore == bufferAfter && an.WantsRegionChangedEvents())
{
// Make the range ordered...
auto beg = cursorBefore;
auto end = cursorAfter;
if (beg > end)
{
std::swap(beg, end);
}
// ...and make it inclusive.
end.x--;
if (end.x < 0)
{
end.y--;
end.x = bufferAfter->GetBufferSize().Width() - 1;
}
an.RegionChanged(beg, end);
}
an.CursorChanged(cursorAfter, false);
}
});
// Don't raise any events for inactive buffers.
if (bufferBefore != &screenInfo)
{
raise.release();
}
return raise;
}
// Routine Description:
// - This routine updates the cursor position. Its input is the non-special
// cased new location of the cursor. For example, if the cursor were being
// moved one space backwards from the left edge of the screen, the X
// coordinate would be -1. This routine would set the X coordinate to
// the right edge of the screen and decrement the Y coordinate by one.
// Arguments:
// - screenInfo - reference to screen buffer information structure.
// - coordCursor - New location of cursor.
// - fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge
// Return Value:
static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, _Inout_opt_ til::CoordType* psScrollY)
{
const auto bufferSize = screenInfo.GetBufferSize().Dimensions();
if (coordCursor.x < 0)
{
if (coordCursor.y > 0)
{
coordCursor.x = bufferSize.width + coordCursor.x;
coordCursor.y = coordCursor.y - 1;
}
else
{
coordCursor.x = 0;
}
}
else if (coordCursor.x >= bufferSize.width)
{
// at end of line. if wrap mode, wrap cursor. otherwise leave it where it is.
if (screenInfo.OutputMode & ENABLE_WRAP_AT_EOL_OUTPUT)
{
coordCursor.y += coordCursor.x / bufferSize.width;
coordCursor.x = coordCursor.x % bufferSize.width;
}
else
{
coordCursor.x = screenInfo.GetTextBuffer().GetCursor().GetPosition().x;
}
}
if (coordCursor.y >= bufferSize.height)
{
auto& buffer = screenInfo.GetTextBuffer();
buffer.IncrementCircularBuffer(buffer.GetCurrentAttributes());
// TODO: This is very bad for performance.
// Track the total scroll offset as an int64 in buffer --> No need to track it here anymore.
if (buffer.IsActiveBuffer())
{
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.ScrollBuffer(-1);
if (const auto renderer = ServiceLocator::LocateGlobals().pRender)
{
static constexpr til::point delta{ 0, -1 };
renderer->TriggerScroll(&delta);
}
}
if (psScrollY)
{
*psScrollY += 1;
}
coordCursor.y = bufferSize.height - 1;
}
LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor));
}
// As the name implies, this writes text without processing its control characters.
static bool _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
{
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
auto& textBuffer = screenInfo.GetTextBuffer();
bool wrapped = false;
RowWriteState state{
.text = text,
.columnLimit = textBuffer.GetSize().RightExclusive(),
};
while (!state.text.empty())
{
auto cursorPosition = textBuffer.GetCursor().GetPosition();
state.columnBegin = cursorPosition.x;
textBuffer.Replace(cursorPosition.y, textBuffer.GetCurrentAttributes(), state);
cursorPosition.x = state.columnEnd;
wrapped = wrapAtEOL && state.columnEnd >= state.columnLimit;
if (wrapped)
{
textBuffer.SetWrapForced(cursorPosition.y, true);
}
AdjustCursorPosition(screenInfo, cursorPosition, psScrollY);
}
return wrapped;
}
// This routine writes a string to the screen while handling control characters.
// `interactive` exists for COOKED_READ_DATA which uses it to transform control characters into visible text like "^X".
// Similarly, `psScrollY` is also used by it to track whether the underlying buffer circled. It requires this information to know where the input line moved to.
void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
{
static constexpr wchar_t tabSpaces[8]{ L' ', L' ', L' ', L' ', L' ', L' ', L' ', L' ' };
auto& textBuffer = screenInfo.GetTextBuffer();
const auto width = textBuffer.GetSize().Width();
auto& cursor = textBuffer.GetCursor();
const auto cursorPosBefore = cursor.GetPosition();
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
const auto beg = text.begin();
const auto end = text.end();
auto it = beg;
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
const auto a11y = raiseAccessibilityEventsOnExit(screenInfo);
const auto snap = screenInfo.SnapOnOutput();
// If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode.
// Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back
// to a position that the Console APIs expect (= not delayed).
if (const auto delayed = cursor.GetDelayEOLWrap(); delayed && wrapAtEOL)
{
auto pos = cursor.GetPosition();
cursor.ResetDelayEOLWrap();
if (delayed == pos)
{
pos.x = 0;
pos.y++;
AdjustCursorPosition(screenInfo, pos, psScrollY);
if (writer)
{
writer.WriteUTF8("\r\n");
}
}
}
// If ENABLE_PROCESSED_OUTPUT is set we search for C0 control characters and handle them like backspace, tab, etc.
// If it's not set, we can just straight up give everything to WriteCharsLegacyUnprocessed.
if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT))
{
const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, text, psScrollY);
if (writer)
{
// We're asked to produce VT output, but also to behave as if these control characters aren't control characters.
// So, to make it work, we simply replace all the control characters with whitespace.
writer.WriteUTF16StripControlChars(text);
if (lastCharWrapped)
{
writer.WriteUTF8("\r\n");
}
writer.Submit();
}
return;
}
while (it != end)
{
const auto nextControlChar = std::find_if(it, end, controlCharPredicate);
if (nextControlChar != it)
{
const std::wstring_view chunk{ it, nextControlChar };
const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, chunk, psScrollY);
it = nextControlChar;
if (writer)
{
writer.WriteUTF16(chunk);
if (lastCharWrapped)
{
writer.WriteUTF8("\r\n");
}
}
}
if (it == end)
{
break;
}
do
{
auto wch = *it;
auto lastCharWrapped = false;
switch (wch)
{
case UNICODE_NULL:
{
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY);
wch = L' ';
break;
}
case UNICODE_BELL:
{
std::ignore = screenInfo.SendNotifyBeep();
break;
}
case UNICODE_BACKSPACE:
{
auto pos = cursor.GetPosition();
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x);
AdjustCursorPosition(screenInfo, pos, psScrollY);
break;
}
case UNICODE_TAB:
{
const auto pos = cursor.GetPosition();
const auto remaining = width - pos.x;
const auto tabCount = gsl::narrow_cast<size_t>(std::min(remaining, 8 - (pos.x & 7)));
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY);
break;
}
case UNICODE_LINEFEED:
{
auto pos = cursor.GetPosition();
// If DISABLE_NEWLINE_AUTO_RETURN is not set, any LF behaves like a CRLF.
if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN))
{
pos.x = 0;
// Setting wch=0 and lastCharWrapped=true will cause the code at the end
// of the loop to emit a CRLF. However, we only do this if the preceding
// character isn't already a CR. We don't want to emit CR CR LF after all.
if (it == beg || it[-1] != '\r')
{
wch = 0;
lastCharWrapped = true;
}
}
textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false);
pos.y = pos.y + 1;
AdjustCursorPosition(screenInfo, pos, psScrollY);
break;
}
case UNICODE_CARRIAGERETURN:
{
auto pos = cursor.GetPosition();
pos.x = 0;
AdjustCursorPosition(screenInfo, pos, psScrollY);
break;
}
default:
{
// As a special favor to incompetent apps that attempt to display control chars,
// convert to corresponding OEM Glyph Chars
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
const auto ch = gsl::narrow_cast<char>(wch);
const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1);
if (result != 1)
{
wch = 0;
}
if (wch)
{
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY);
}
break;
}
}
if (writer)
{
if (wch)
{
writer.WriteUCS2(wch);
}
if (lastCharWrapped)
{
writer.WriteUTF8("\r\n");
}
}
++it;
} while (it != end && controlCharPredicate(*it));
}
if (writer)
{
writer.Submit();
}
}
// This is the main entrypoint for conhost to write VT to the buffer.
// This wrapper around StateMachine exists so that we can add the necessary ConPTY transformations.
void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& stateMachine = screenInfo.GetStateMachine();
// If the given screenInfo is the alternate screen buffer, disabling the alternate screen buffer in this
// VT payload will cause the pointer to be invalidated. We thus need to get all the information we need now.
const auto disableNewlineTranslation = WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN);
// When switch between the main and alt-buffer SCREEN_INFORMATION::GetActiveBuffer()
// may change, so get the VtIo reference now, just in case.
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
const auto a11y = raiseAccessibilityEventsOnExit(screenInfo);
const auto snap = screenInfo.SnapOnOutput();
stateMachine.ProcessString(str);
if (writer)
{
const auto& injections = stateMachine.GetInjections();
size_t offset = 0;
// DISABLE_NEWLINE_AUTO_RETURN not being set is equivalent to a LF -> CRLF translation.
const auto write = [&](size_t beg, size_t end) {
const auto chunk = til::safe_slice_abs(str, beg, end);
if (disableNewlineTranslation)
{
writer.WriteUTF16(chunk);
}
else
{
writer.WriteUTF16TranslateCRLF(chunk);
}
};
// When we encounter something like a RIS (hard reset), we must re-enable
// modes that we rely on (like the Win32 Input Mode). To do this, the VT
// parser tells us the positions of any such relevant VT sequences.
for (const auto& injection : injections)
{
write(offset, injection.offset);
offset = injection.offset;
static constexpr std::array<std::string_view, 3> mapping{ {
{ "\x1b[?1004h\x1b[?9001h" }, // RIS: Focus Event Mode + Win32 Input Mode
{ "\x1b[?1004h" }, // DECSET_FOCUS: Focus Event Mode
{ "\x1b[?9001h" }, // Win32 Input Mode
} };
static_assert(static_cast<size_t>(InjectionType::Count) == mapping.size(), "you need to update the mapping array");
writer.WriteUTF8(mapping[static_cast<size_t>(injection.type)]);
}
write(offset, std::wstring_view::npos);
writer.Submit();
}
}
// Erases all contents of the given screenInfo, including the current screen and scrollback.
void WriteClearScreen(SCREEN_INFORMATION& screenInfo)
{
WriteCharsVT(
screenInfo,
L"\x1b[H" // CUP to home
L"\x1b[2J" // Erase in Display: clear the screen
L"\x1b[3J" // Erase in Display: clear the scrollback buffer
);
}
// Routine Description:
// - Takes the given text and inserts it into the given screen buffer.
// Note:
// - Console lock must be held when calling this routine
// - String has been translated to unicode at this point.
// Arguments:
// - pwchBuffer - wide character text to be inserted into buffer
// - pcbBuffer - byte count of pwchBuffer on the way in, number of bytes consumed on the way out.
// - screenInfo - Screen Information class to write the text into at the current cursor position
[[nodiscard]] HRESULT DoWriteConsole(SCREEN_INFORMATION& screenInfo, std::wstring_view str)
try
{
if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT))
{
WriteCharsLegacy(screenInfo, str, nullptr);
}
else
{
WriteCharsVT(screenInfo, str);
}
return S_OK;
}
CATCH_RETURN()
// Routine Description:
// - Writes non-Unicode formatted data into the given console output object.
// - This method will convert from the given input into wide characters before chain calling the wide character version of the function.
// It uses the current Output Codepage for conversions (set via SetConsoleOutputCP).
// - NOTE: This may be blocked for various console states and will return a wait context pointer if necessary.
// Arguments:
// - context - the console output object to write the new text into
// - buffer - char/byte text buffer provided by client application to insert
// - read - character count of the number of characters (also bytes because A version) we were able to insert before returning
// - waiter - If we are blocked from writing now and need to wait, this is filled with contextual data for the server to restore the call later
// Return Value:
// - S_OK if successful.
// - S_OK if we need to wait (check if ppWaiter is not nullptr).
// - Or a suitable HRESULT code for math/string/memory failures.
[[nodiscard]] HRESULT ApiRoutines::WriteConsoleAImpl(IConsoleOutputObject& context,
const std::string_view buffer,
size_t& read,
CONSOLE_API_MSG* pWaitReplyMessage) noexcept
{
try
{
// Ensure output variables are initialized.
read = 0;
if (buffer.empty())
{
return S_OK;
}
LockConsole();
auto unlock{ wil::scope_exit([&] { UnlockConsole(); }) };
auto& screenInfo{ context.GetActiveBuffer() };
const auto& consoleInfo{ ServiceLocator::LocateGlobals().getConsoleInformation() };
const auto codepage{ consoleInfo.OutputCP };
auto leadByteCaptured{ false };
auto leadByteConsumed{ false };
std::wstring wstr{};
static til::u8state u8State{};
// Convert our input parameters to Unicode
if (codepage == CP_UTF8)
{
RETURN_IF_FAILED(til::u8u16(buffer, wstr, u8State));
read = buffer.size();
}
else
{
// In case the codepage changes from UTF-8 to another,
// we discard partials that might still be cached.
u8State.reset();
int mbPtrLength{};
RETURN_IF_FAILED(SizeTToInt(buffer.size(), &mbPtrLength));
// (buffer.size() + 2) I think because we might be shoving another unicode char
// from screenInfo->WriteConsoleDbcsLeadByte in front
// because we previously checked that buffer.size() fits into an int, +2 won't cause an overflow of size_t
wstr.resize(buffer.size() + 2);
auto wcPtr{ wstr.data() };
auto mbPtr{ buffer.data() };
size_t dbcsLength{};
if (screenInfo.WriteConsoleDbcsLeadByte[0] != 0 && gsl::narrow_cast<byte>(*mbPtr) >= byte{ ' ' })
{
// there was a portion of a dbcs character stored from a previous
// call so we take the 2nd half from mbPtr[0], put them together
// and write the wide char to wcPtr[0]
screenInfo.WriteConsoleDbcsLeadByte[1] = gsl::narrow_cast<byte>(*mbPtr);
try
{
const auto wFromComplemented{
ConvertToW(codepage, { reinterpret_cast<const char*>(screenInfo.WriteConsoleDbcsLeadByte), ARRAYSIZE(screenInfo.WriteConsoleDbcsLeadByte) })
};
FAIL_FAST_IF(wFromComplemented.size() != 1);
dbcsLength = sizeof(wchar_t);
wcPtr[0] = wFromComplemented.at(0);
mbPtr++;
}
catch (...)
{
dbcsLength = 0;
}
// this looks weird to be always incrementing even if the conversion failed, but this is the
// original behavior so it's left unchanged.
wcPtr++;
mbPtrLength--;
// Note that we used a stored lead byte from a previous call in order to complete this write
// Use this to offset the "number of bytes consumed" calculation at the end by -1 to account
// for using a byte we had internally, not off the stream.
leadByteConsumed = true;
}
screenInfo.WriteConsoleDbcsLeadByte[0] = 0;
// if the last byte in mbPtr is a lead byte for the current code page,
// save it for the next time this function is called and we can piece it
// back together then
if (mbPtrLength != 0 && CheckBisectStringA(const_cast<char*>(mbPtr), mbPtrLength, &consoleInfo.OutputCPInfo))
{
screenInfo.WriteConsoleDbcsLeadByte[0] = gsl::narrow_cast<byte>(mbPtr[mbPtrLength - 1]);
mbPtrLength--;
// Note that we captured a lead byte during this call, but won't actually draw it until later.
// Use this to offset the "number of bytes consumed" calculation at the end by +1 to account
// for taking a byte off the stream.
leadByteCaptured = true;
}
if (mbPtrLength != 0)
{
// convert the remaining bytes in mbPtr to wide chars
mbPtrLength = sizeof(wchar_t) * MultiByteToWideChar(codepage, 0, mbPtr, mbPtrLength, wcPtr, mbPtrLength);
}
wstr.resize((dbcsLength + mbPtrLength) / sizeof(wchar_t));
}
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING)))
{
const auto waiter = new WriteData(screenInfo, std::move(wstr), gci.OutputCP);
// If there is a waiter, then we need to stow some additional information in the wait structure so
// we can synthesize the correct byte count later when the wait routine is triggered.
if (CP_UTF8 != codepage)
{
// For non-UTF8 codepages, save the lead byte captured/consumed data so we can +1 or -1 the final decoded count
// in the WaitData::Notify method later.
waiter->SetLeadByteAdjustmentStatus(leadByteCaptured, leadByteConsumed);
}
else
{
// For UTF8 codepages, just remember the consumption count from the UTF-8 parser.
waiter->SetUtf8ConsumedCharacters(read);
}
std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, waiter);
return CONSOLE_STATUS_WAIT;
}
// Make the W version of the call
const auto hr = DoWriteConsole(screenInfo, wstr);
// Calculate how many bytes of the original A buffer were consumed in the W version of the call to satisfy mbBufferRead.
// For UTF-8 conversions, we've already returned this information above.
if (CP_UTF8 != codepage)
{
size_t mbBufferRead{};
// Start by counting the number of A bytes we used in printing our W string to the screen.
try
{
mbBufferRead = GetALengthFromW(codepage, wstr);
}
CATCH_LOG();
// If we captured a byte off the string this time around up above, it means we didn't feed
// it into the WriteConsoleW above, and therefore its consumption isn't accounted for
// in the count we just made. Add +1 to compensate.
if (leadByteCaptured)
{
mbBufferRead++;
}
// If we consumed an internally-stored lead byte this time around up above, it means that we
// fed a byte into WriteConsoleW that wasn't a part of this particular call's request.
// We need to -1 to compensate and tell the caller the right number of bytes consumed this request.
if (leadByteConsumed)
{
mbBufferRead--;
}
read = mbBufferRead;
}
return hr;
}
CATCH_RETURN();
}
// Routine Description:
// - Writes Unicode formatted data into the given console output object.
// - NOTE: This may be blocked for various console states and will return a wait context pointer if necessary.
// Arguments:
// - OutContext - the console output object to write the new text into
// - pwsTextBuffer - wide character text buffer provided by client application to insert
// - cchTextBufferLength - text buffer counted in characters
// - pcchTextBufferRead - character count of the number of characters we were able to insert before returning
// - ppWaiter - If we are blocked from writing now and need to wait, this is filled with contextual data for the server to restore the call later
// Return Value:
// - S_OK if successful.
// - S_OK if we need to wait (check if ppWaiter is not nullptr).
// - Or a suitable HRESULT code for math/string/memory failures.
[[nodiscard]] HRESULT ApiRoutines::WriteConsoleWImpl(IConsoleOutputObject& context,
const std::wstring_view buffer,
size_t& read,
CONSOLE_API_MSG* pWaitReplyMessage) noexcept
{
try
{
LockConsole();
auto unlock = wil::scope_exit([&] { UnlockConsole(); });
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING)))
{
std::ignore = ConsoleWaitQueue::s_CreateWait(pWaitReplyMessage, new WriteData(context, std::wstring{ buffer }, gci.OutputCP));
return CONSOLE_STATUS_WAIT;
}
read = 0;
auto Status = DoWriteConsole(context, buffer);
read = buffer.size();
return Status;
}
CATCH_RETURN();
}