@@ -1404,6 +1404,8 @@ NT_CATCH_RETURN()
14041404
14051405 if (SUCCEEDED_NTSTATUS (status))
14061406 {
1407+ SetConptyCursorPositionMayBeWrong ();
1408+
14071409 // Fire off an event to let accessibility apps know the layout has changed.
14081410 if (IsActiveScreenBuffer ())
14091411 {
@@ -1422,6 +1424,86 @@ NT_CATCH_RETURN()
14221424 return status;
14231425}
14241426
1427+ // If we're ConPTY, our copy of the buffer may be out of sync with the terminal,
1428+ // because our VT, resize reflow, etc., implementation may be different.
1429+ // For some operations we set a flag to indicate the cursor position may be wrong.
1430+ // For GetConsoleScreenBufferInfo(Ex) we then fetch the latest position from the terminal.
1431+ // This fixes some of the most glaring out of sync issues. See GH#18725.
1432+ bool SCREEN_INFORMATION::ConptyCursorPositionMayBeWrong () const noexcept
1433+ {
1434+ return _conptyCursorPositionMayBeWrong.load (std::memory_order_relaxed);
1435+ }
1436+
1437+ // This should be called whenever we do something that may desynchronize
1438+ // our cursor position from the terminal's, e.g. a buffer resize.
1439+ // See ConptyCursorPositionMayBeWrong().
1440+ void SCREEN_INFORMATION::SetConptyCursorPositionMayBeWrong () noexcept
1441+ {
1442+ auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
1443+ assert (gci.IsConsoleLocked ());
1444+
1445+ if (gci.IsInVtIoMode ())
1446+ {
1447+ _conptyCursorPositionMayBeWrong.store (true , std::memory_order_relaxed);
1448+ }
1449+ }
1450+
1451+ // This should be called whenever we've synchronized our cursor position again.
1452+ // See ConptyCursorPositionMayBeWrong().
1453+ void SCREEN_INFORMATION::ResetConptyCursorPositionMayBeWrong () noexcept
1454+ {
1455+ _conptyCursorPositionMayBeWrong.store (false , std::memory_order_relaxed);
1456+ til::atomic_notify_all (_conptyCursorPositionMayBeWrong);
1457+ }
1458+
1459+ // Call this to synchronously wait until the ConPTY cursor position
1460+ // is known to be correct again. To do so, this emits a DSR CPR sequence
1461+ // and waits for a response from the terminal.
1462+ // See ConptyCursorPositionMayBeWrong().
1463+ void SCREEN_INFORMATION::WaitForConptyCursorPositionToBeSynchronized () noexcept
1464+ {
1465+ if (!_conptyCursorPositionMayBeWrong.load (std::memory_order_relaxed))
1466+ {
1467+ return ;
1468+ }
1469+
1470+ auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
1471+
1472+ {
1473+ gci.LockConsole ();
1474+ const auto exit = wil::scope_exit ([&] { gci.UnlockConsole (); });
1475+ auto writer = gci.GetVtWriterForBuffer (this );
1476+
1477+ if (!writer || !writer.WriteDSRCPR ())
1478+ {
1479+ _conptyCursorPositionMayBeWrong.store (false , std::memory_order_relaxed);
1480+ return ;
1481+ }
1482+
1483+ writer.Submit ();
1484+ }
1485+
1486+ // If you were to hold the lock, the ConPTY input thread couldn't
1487+ // process any input and thus couldn't update the cursor position.
1488+ assert (!gci.IsConsoleLocked ());
1489+
1490+ for (;;)
1491+ {
1492+ if (!_conptyCursorPositionMayBeWrong.load (std::memory_order::relaxed))
1493+ {
1494+ break ;
1495+ }
1496+
1497+ // atomic_wait() returns false when the timeout expires.
1498+ // Technically we should decrement the timeout with each iteration,
1499+ // but I suspect infinite spurious wake-ups are a theoretical problem.
1500+ if (!til::atomic_wait (_conptyCursorPositionMayBeWrong, true , 500 ))
1501+ {
1502+ break ;
1503+ }
1504+ }
1505+ }
1506+
14251507// Routine Description:
14261508// - Given a rectangle containing screen buffer coordinates (character-level positioning, not pixel)
14271509// This method will trim the rectangle to ensure it is within the buffer.
0 commit comments