mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-25 15:41:36 +00:00
`Terminal` is used concurrently by at least 4 threads. The table below lists the class members and the threads that access them to the best of my knowledge. Where: * UI: UI Thread * BG: Background worker threads (`winrt::resume_background`) * RD: Render thread * VT: VT connection thread | | UI | BG | RD | VT | |------------------------------------|----|----|----|----| | `_pfnWriteInput` | x | x | | x | | `_pfnWarningBell` | | | | x | | `_pfnTitleChanged` | | | | x | | `_pfnCopyToClipboard` | | | | x | | `_pfnScrollPositionChanged` | x | x | | x | | `_pfnCursorPositionChanged` | | | | x | | `_pfnTaskbarProgressChanged` | | | | x | | `_pfnShowWindowChanged` | | | | x | | `_pfnPlayMidiNote` | | | | x | | `_pfnCompletionsChanged` | | | | x | | `_renderSettings` | x | | x | x | | `_stateMachine` | x | | | x | | `_terminalInput` | x | | | x | | `_title` | x | | x | x | | `_startingTitle` | x | | x | | | `_startingTabColor` | x | | | | | `_defaultCursorShape` | x | | | x | | `_systemMode` | | x | x | x | | `_snapOnInput` | x | x | | | | `_altGrAliasing` | x | | | | | `_suppressApplicationTitle` | x | | | x | | `_trimBlockSelection` | x | | | | | `_autoMarkPrompts` | x | | | | | `_taskbarState` | x | | | x | | `_taskbarProgress` | x | | | x | | `_workingDirectory` | x | | | x | | `_fontInfo` | x | | x | | | `_selection` | x | x | x | x | | `_blockSelection` | x | x | x | | | `_wordDelimiters` | x | x | | | | `_multiClickSelectionMode` | x | x | x | | | `_selectionMode` | x | x | x | | | `_selectionIsTargetingUrl` | x | x | x | | | `_selectionEndpoint` | x | x | x | | | `_anchorInactiveSelectionEndpoint` | x | x | x | | | `_mainBuffer` | x | x | x | x | | `_altBuffer` | x | x | x | x | | `_mutableViewport` | x | | x | x | | `_scrollbackLines` | x | | | | | `_detectURLs` | x | | | | | `_altBufferSize` | x | x | x | x | | `_deferredResize` | x | | | x | | `_scrollOffset` | x | x | x | x | | `_patternIntervalTree` | x | x | x | x | | `_lastKeyEventCodes` | x | | | | | `_currentPromptState` | x | | | x | Only 7 members are specific to one thread and don't require locking. All other members require some for of locking to be safe for use. To address the issue this changeset adds `LockForReading/LockForWriting` calls everywhere `_terminal` is accessed in `ControlCore/HwndTerminal`. Additionally, to ensure these issues don't pop up anymore, it adds to all `Terminal` functions a debug assertion that the lock is being held. Finally, because this changeset started off rather modest, it contains changes that I initially made without being aware about the extent of the issue. It simplifies the access around `_patternIntervalTree` by making `_InvalidatePatternTree()` directly use that member. Furthermore, it simplifies `_terminal->SetCursorOn(!IsCursorOn())` to `BlinkCursor()`, allowing the code to be shared with `HwndTerminal`. Ideally `Terminal` should not be that much of a class so that we don't need such coarse locking. Splitting out selection and rendering state should allow deduplicating code with conhost and use finer locking. Closes #9617 ## Validation Steps Performed I tried to use as many Windows Terminal features as I could and fixed every occurrence of `_assertLocked()` failures.
228 lines
6.0 KiB
C++
228 lines
6.0 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "Terminal.hpp"
|
|
#include <DefaultSettings.h>
|
|
|
|
using namespace Microsoft::Terminal::Core;
|
|
using namespace Microsoft::Console::Types;
|
|
using namespace Microsoft::Console::Render;
|
|
|
|
Viewport Terminal::GetViewport() noexcept
|
|
{
|
|
return _GetVisibleViewport();
|
|
}
|
|
|
|
til::point Terminal::GetTextBufferEndPosition() const noexcept
|
|
{
|
|
// We use the end line of mutableViewport as the end
|
|
// of the text buffer, it always moves with the written
|
|
// text
|
|
return { _GetMutableViewport().Width() - 1, ViewEndIndex() };
|
|
}
|
|
|
|
const TextBuffer& Terminal::GetTextBuffer() const noexcept
|
|
{
|
|
return _activeBuffer();
|
|
}
|
|
|
|
const FontInfo& Terminal::GetFontInfo() const noexcept
|
|
{
|
|
_assertLocked();
|
|
return _fontInfo;
|
|
}
|
|
|
|
void Terminal::SetFontInfo(const FontInfo& fontInfo)
|
|
{
|
|
_assertLocked();
|
|
_fontInfo = fontInfo;
|
|
}
|
|
|
|
til::point Terminal::GetCursorPosition() const noexcept
|
|
{
|
|
const auto& cursor = _activeBuffer().GetCursor();
|
|
return cursor.GetPosition();
|
|
}
|
|
|
|
bool Terminal::IsCursorVisible() const noexcept
|
|
{
|
|
const auto& cursor = _activeBuffer().GetCursor();
|
|
return cursor.IsVisible() && !cursor.IsPopupShown();
|
|
}
|
|
|
|
bool Terminal::IsCursorOn() const noexcept
|
|
{
|
|
const auto& cursor = _activeBuffer().GetCursor();
|
|
return cursor.IsOn();
|
|
}
|
|
|
|
ULONG Terminal::GetCursorPixelWidth() const noexcept
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ULONG Terminal::GetCursorHeight() const noexcept
|
|
{
|
|
return _activeBuffer().GetCursor().GetSize();
|
|
}
|
|
|
|
CursorType Terminal::GetCursorStyle() const noexcept
|
|
{
|
|
return _activeBuffer().GetCursor().GetType();
|
|
}
|
|
|
|
bool Terminal::IsCursorDoubleWidth() const
|
|
{
|
|
const auto& buffer = _activeBuffer();
|
|
const auto position = buffer.GetCursor().GetPosition();
|
|
return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
|
|
}
|
|
|
|
const std::vector<RenderOverlay> Terminal::GetOverlays() const noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
const bool Terminal::IsGridLineDrawingAllowed() noexcept
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(uint16_t id) const
|
|
{
|
|
return _activeBuffer().GetHyperlinkUriFromId(id);
|
|
}
|
|
|
|
const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uint16_t id) const
|
|
{
|
|
return _activeBuffer().GetCustomIdFromId(id);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Gets the regex pattern ids of a location
|
|
// Arguments:
|
|
// - The location
|
|
// Return value:
|
|
// - The pattern IDs of the location
|
|
const std::vector<size_t> Terminal::GetPatternId(const til::point location) const
|
|
{
|
|
_assertLocked();
|
|
|
|
// Look through our interval tree for this location
|
|
const auto intervals = _patternIntervalTree.findOverlapping({ location.x + 1, location.y }, location);
|
|
if (intervals.size() == 0)
|
|
{
|
|
return {};
|
|
}
|
|
else
|
|
{
|
|
std::vector<size_t> result{};
|
|
for (const auto& interval : intervals)
|
|
{
|
|
result.emplace_back(interval.value);
|
|
}
|
|
return result;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::pair<COLORREF, COLORREF> Terminal::GetAttributeColors(const TextAttribute& attr) const noexcept
|
|
{
|
|
return GetRenderSettings().GetAttributeColors(attr);
|
|
}
|
|
|
|
std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSelectionRects() noexcept
|
|
try
|
|
{
|
|
std::vector<Viewport> result;
|
|
|
|
for (const auto& lineRect : _GetSelectionRects())
|
|
{
|
|
result.emplace_back(Viewport::FromInclusive(lineRect));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_CAUGHT_EXCEPTION();
|
|
return {};
|
|
}
|
|
|
|
void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd)
|
|
{
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 26496) // cpp core checks wants these const, but they're decremented below.
|
|
auto realCoordStart = coordStart;
|
|
auto realCoordEnd = coordEnd;
|
|
#pragma warning(pop)
|
|
|
|
auto notifyScrollChange = false;
|
|
if (coordStart.y < _VisibleStartIndex())
|
|
{
|
|
// recalculate the scrollOffset
|
|
_scrollOffset = ViewStartIndex() - coordStart.y;
|
|
notifyScrollChange = true;
|
|
}
|
|
else if (coordEnd.y > _VisibleEndIndex())
|
|
{
|
|
// recalculate the scrollOffset, note that if the found text is
|
|
// beneath the current visible viewport, it may be within the
|
|
// current mutableViewport and the scrollOffset will be smaller
|
|
// than 0
|
|
_scrollOffset = std::max(0, ViewStartIndex() - coordStart.y);
|
|
notifyScrollChange = true;
|
|
}
|
|
|
|
if (notifyScrollChange)
|
|
{
|
|
_activeBuffer().TriggerScroll();
|
|
_NotifyScrollEvent();
|
|
}
|
|
|
|
realCoordStart.y -= _VisibleStartIndex();
|
|
realCoordEnd.y -= _VisibleStartIndex();
|
|
|
|
SetSelectionAnchor(realCoordStart);
|
|
SetSelectionEnd(realCoordEnd, SelectionExpansion::Char);
|
|
}
|
|
|
|
const std::wstring_view Terminal::GetConsoleTitle() const noexcept
|
|
{
|
|
_assertLocked();
|
|
if (_title.has_value())
|
|
{
|
|
return *_title;
|
|
}
|
|
return _startingTitle;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Lock the terminal for reading the contents of the buffer. Ensures that the
|
|
// contents of the terminal won't be changed in the middle of a paint
|
|
// operation.
|
|
// Callers should make sure to also call Terminal::UnlockConsole once
|
|
// they're done with any querying they need to do.
|
|
void Terminal::LockConsole() noexcept
|
|
{
|
|
_readWriteLock.lock();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Unlocks the terminal after a call to Terminal::LockConsole.
|
|
void Terminal::UnlockConsole() noexcept
|
|
{
|
|
_readWriteLock.unlock();
|
|
}
|
|
|
|
const bool Terminal::IsUiaDataInitialized() const noexcept
|
|
{
|
|
// GH#11135: Windows Terminal needs to create and return an automation peer
|
|
// when a screen reader requests it. However, the terminal might not be fully
|
|
// initialized yet. So we use this to check if any crucial components of
|
|
// UiaData are not yet initialized.
|
|
_assertLocked();
|
|
return !!_mainBuffer;
|
|
}
|