Merge branch 'dev/migrie/interactivity-projection-000' into dev/migrie/oop/sample-project
This commit is contained in:
commit
4a66f61c6e
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
|
@ -2358,6 +2358,7 @@ testmddefinition
|
|||
testmode
|
||||
testname
|
||||
testnameprefix
|
||||
testnetv
|
||||
TESTNULL
|
||||
testpass
|
||||
testpasses
|
||||
|
|
|
@ -20,6 +20,12 @@ jobs:
|
|||
parameters:
|
||||
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}
|
||||
|
||||
# It appears that the Component Governance build task that gets automatically injected stopped working
|
||||
# when we renamed our main branch.
|
||||
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
|
||||
displayName: 'Component Detection'
|
||||
condition: and(succeededOrFailed(), not(eq(variables['Build.Reason'], 'PullRequest')))
|
||||
|
||||
- template: helix-runtests-job.yml
|
||||
parameters:
|
||||
name: 'RunTestsInHelix'
|
||||
|
|
|
@ -62,7 +62,8 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
void SettingsTab::_MakeTabViewItem()
|
||||
{
|
||||
TabViewItem(::winrt::MUX::Controls::TabViewItem{});
|
||||
TabBase::_MakeTabViewItem();
|
||||
|
||||
Title(RS_(L"SettingsTab"));
|
||||
TabViewItem().Header(winrt::box_value(Title()));
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace winrt::TerminalApp::implementation
|
|||
void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
|
||||
|
||||
private:
|
||||
void _MakeTabViewItem();
|
||||
void _MakeTabViewItem() override;
|
||||
winrt::fire_and_forget _CreateIcon();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,9 +46,17 @@ namespace winrt::TerminalApp::implementation
|
|||
auto weakThis{ get_weak() };
|
||||
|
||||
// Build the menu
|
||||
Controls::MenuFlyout newTabFlyout;
|
||||
_AppendCloseMenuItems(newTabFlyout);
|
||||
TabViewItem().ContextFlyout(newTabFlyout);
|
||||
Controls::MenuFlyout contextMenuFlyout;
|
||||
// GH#5750 - When the context menu is dismissed with ESC, toss the focus
|
||||
// back to our control.
|
||||
contextMenuFlyout.Closed([weakThis](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_RequestFocusActiveControlHandlers();
|
||||
}
|
||||
});
|
||||
_AppendCloseMenuItems(contextMenuFlyout);
|
||||
TabViewItem().ContextFlyout(contextMenuFlyout);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -223,4 +231,24 @@ namespace winrt::TerminalApp::implementation
|
|||
toolTip.Content(textBlock);
|
||||
WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes a TabViewItem for this Tab instance.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TabBase::_MakeTabViewItem()
|
||||
{
|
||||
TabViewItem(::winrt::MUX::Controls::TabViewItem{});
|
||||
|
||||
// GH#3609 If the tab was tapped, and no one else was around to handle
|
||||
// it, then ask our parent to toss focus into the active control.
|
||||
TabViewItem().Tapped([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_RequestFocusActiveControlHandlers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ namespace winrt::TerminalApp::implementation
|
|||
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
|
||||
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
|
||||
|
||||
WINRT_CALLBACK(RequestFocusActiveControl, winrt::delegate<void()>);
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
WINRT_CALLBACK(CloseRequested, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
|
@ -52,6 +54,8 @@ namespace winrt::TerminalApp::implementation
|
|||
virtual void _CreateContextMenu();
|
||||
virtual winrt::hstring _CreateToolTipTitle();
|
||||
|
||||
virtual void _MakeTabViewItem();
|
||||
|
||||
void _AppendCloseMenuItems(winrt::Windows::UI::Xaml::Controls::MenuFlyout flyout);
|
||||
void _EnableCloseMenuItems();
|
||||
void _CloseTabsAfter();
|
||||
|
|
|
@ -215,7 +215,9 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
});
|
||||
|
||||
newTabImpl->TabRenamerDeactivated([weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
|
||||
// The tab might want us to toss focus into the control, especially when
|
||||
// transient UIs (like the context menu, or the renamer) are dismissed.
|
||||
newTabImpl->RequestFocusActiveControl([weakThis{ get_weak() }]() {
|
||||
if (const auto page{ weakThis.get() })
|
||||
{
|
||||
page->_FocusCurrentTab(false);
|
||||
|
|
|
@ -53,6 +53,15 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
});
|
||||
|
||||
// GH#9162 - when the header is done renaming, ask for focus to be
|
||||
// tossed back to the control, rather into ourselves.
|
||||
_headerControl.RenameEnded([weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_RequestFocusActiveControlHandlers();
|
||||
}
|
||||
});
|
||||
|
||||
_UpdateHeaderControlMaxWidth();
|
||||
|
||||
// Use our header control as the TabViewItem's header
|
||||
|
@ -83,7 +92,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
void TerminalTab::_MakeTabViewItem()
|
||||
{
|
||||
TabViewItem(::winrt::MUX::Controls::TabViewItem{});
|
||||
TabBase::_MakeTabViewItem();
|
||||
|
||||
TabViewItem().DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
|
@ -878,14 +887,23 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
|
||||
// Build the menu
|
||||
Controls::MenuFlyout newTabFlyout;
|
||||
Controls::MenuFlyout contextMenuFlyout;
|
||||
Controls::MenuFlyoutSeparator menuSeparator;
|
||||
newTabFlyout.Items().Append(chooseColorMenuItem);
|
||||
newTabFlyout.Items().Append(renameTabMenuItem);
|
||||
newTabFlyout.Items().Append(duplicateTabMenuItem);
|
||||
newTabFlyout.Items().Append(menuSeparator);
|
||||
_AppendCloseMenuItems(newTabFlyout);
|
||||
TabViewItem().ContextFlyout(newTabFlyout);
|
||||
contextMenuFlyout.Items().Append(chooseColorMenuItem);
|
||||
contextMenuFlyout.Items().Append(renameTabMenuItem);
|
||||
contextMenuFlyout.Items().Append(duplicateTabMenuItem);
|
||||
contextMenuFlyout.Items().Append(menuSeparator);
|
||||
|
||||
// GH#5750 - When the context menu is dismissed with ESC, toss the focus
|
||||
// back to our control.
|
||||
contextMenuFlyout.Closed([weakThis](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_RequestFocusActiveControlHandlers();
|
||||
}
|
||||
});
|
||||
_AppendCloseMenuItems(contextMenuFlyout);
|
||||
TabViewItem().ContextFlyout(contextMenuFlyout);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -91,7 +91,6 @@ namespace winrt::TerminalApp::implementation
|
|||
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
|
||||
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
|
||||
DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>);
|
||||
FORWARDED_TYPED_EVENT(TabRenamerDeactivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, (&_headerControl), RenameEnded);
|
||||
TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable);
|
||||
|
||||
private:
|
||||
|
@ -120,7 +119,7 @@ namespace winrt::TerminalApp::implementation
|
|||
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
|
||||
void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
|
||||
void _MakeTabViewItem();
|
||||
void _MakeTabViewItem() override;
|
||||
|
||||
winrt::fire_and_forget _UpdateHeaderControlMaxWidth();
|
||||
|
||||
|
|
|
@ -101,27 +101,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
{
|
||||
Close();
|
||||
|
||||
// Before destroying this instance we must ensure that we destroy the _renderer
|
||||
// before the _renderEngine, as well as calling _renderer->TriggerTeardown().
|
||||
// _renderEngine will be destroyed naturally after this ~destructor() returns.
|
||||
|
||||
decltype(_renderer) renderer;
|
||||
if (_renderer)
|
||||
{
|
||||
// GH#8734:
|
||||
// We lock the terminal here to make sure it isn't still being
|
||||
// used in the connection thread before we destroy the renderer.
|
||||
// However, we must unlock it again prior to triggering the
|
||||
// teardown, to avoid the render thread being deadlocked. The
|
||||
// renderer may be waiting to acquire the terminal lock, while
|
||||
// we're waiting for the renderer to finish.
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
_renderer.swap(renderer);
|
||||
}
|
||||
|
||||
if (renderer)
|
||||
{
|
||||
renderer->TriggerTeardown();
|
||||
_renderer->TriggerTeardown();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,7 +480,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
return winrt::hstring{ _terminal->GetHyperlinkAtPosition(pos) };
|
||||
}
|
||||
|
||||
winrt::hstring ControlCore::GetHoveredUriText() const
|
||||
winrt::hstring ControlCore::HoveredUriText() const
|
||||
{
|
||||
auto lock = _terminal->LockForReading(); // Lock for the duration of our reads.
|
||||
if (_lastHoveredCell.has_value())
|
||||
|
@ -508,9 +490,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
return {};
|
||||
}
|
||||
|
||||
Windows::Foundation::IReference<Core::Point> ControlCore::GetHoveredCell() const
|
||||
Windows::Foundation::IReference<Core::Point> ControlCore::HoveredCell() const
|
||||
{
|
||||
return _lastHoveredCell.has_value() ? Windows::Foundation::IReference<Core::Point>(til::point{ _lastHoveredCell.value() }) : nullptr;
|
||||
return _lastHoveredCell.has_value() ? Windows::Foundation::IReference<Core::Point>(_lastHoveredCell.value()) : nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -1117,9 +1099,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
Windows::Foundation::Collections::IVector<winrt::hstring> ControlCore::SelectedText(bool trimTrailingWhitespace) const
|
||||
{
|
||||
// RetrieveSelectedTextFromBuffer will lock while it's reading
|
||||
std::vector<std::wstring> internalResult{ _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text };
|
||||
auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text };
|
||||
|
||||
auto result = winrt::single_threaded_vector<winrt::hstring>();
|
||||
|
||||
for (const auto& row : internalResult)
|
||||
{
|
||||
result.Append(winrt::hstring{ row });
|
||||
|
@ -1219,7 +1202,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
}
|
||||
}
|
||||
|
||||
uint64_t ControlCore::GetSwapChainHandle() const
|
||||
uint64_t ControlCore::SwapChainHandle() const
|
||||
{
|
||||
// This is called by:
|
||||
// * TermControl::RenderEngineSwapChainChanged, who is only registered
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
void UpdateAppearance(const IControlAppearance& newAppearance);
|
||||
void SizeChanged(const double width, const double height);
|
||||
void ScaleChanged(const double scale);
|
||||
uint64_t GetSwapChainHandle() const;
|
||||
uint64_t SwapChainHandle() const;
|
||||
|
||||
void AdjustFontSize(int fontSizeDelta);
|
||||
void ResetFontSize();
|
||||
|
@ -74,8 +74,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
void UpdatePatternLocations();
|
||||
void UpdateHoveredCell(Windows::Foundation::IReference<Core::Point> terminalPosition);
|
||||
winrt::hstring GetHyperlink(const til::point position) const;
|
||||
winrt::hstring GetHoveredUriText() const;
|
||||
Windows::Foundation::IReference<Core::Point> GetHoveredCell() const;
|
||||
winrt::hstring HoveredUriText() const;
|
||||
Windows::Foundation::IReference<Core::Point> HoveredCell() const;
|
||||
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
|
||||
|
@ -176,8 +176,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr };
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
|
||||
// NOTE: _renderEngine must be ordered before _renderer.
|
||||
//
|
||||
// As _renderer has a dependency on _renderEngine (through a raw pointer)
|
||||
// we must ensure the _renderer is deallocated first.
|
||||
// (C++ class members are destroyed in reverse order.)
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine{ nullptr };
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
|
||||
|
||||
IControlSettings _settings{ nullptr };
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Microsoft.Terminal.Control
|
|||
void UpdateSettings(IControlSettings settings);
|
||||
void UpdateAppearance(IControlAppearance appearance);
|
||||
|
||||
UInt64 GetSwapChainHandle();
|
||||
UInt64 SwapChainHandle { get; };
|
||||
|
||||
Windows.Foundation.Size FontSize { get; };
|
||||
String FontFaceName { get; };
|
||||
|
@ -52,30 +52,30 @@ namespace Microsoft.Terminal.Control
|
|||
void UpdateHoveredCell(Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> terminalPosition);
|
||||
|
||||
void ResetFontSize();
|
||||
void AdjustFontSize(Int32 fontSize);
|
||||
void AdjustFontSize(Int32 fontSizeDelta);
|
||||
void SizeChanged(Double width, Double height);
|
||||
void ScaleChanged(Double scale);
|
||||
|
||||
void ToggleShaderEffects();
|
||||
void ToggleReadOnlyMode();
|
||||
|
||||
Microsoft.Terminal.Core.Point CursorPosition();
|
||||
Microsoft.Terminal.Core.Point CursorPosition { get; };
|
||||
void ResumeRendering();
|
||||
void BlinkAttributeTick();
|
||||
void UpdatePatternLocations();
|
||||
void Search(String text, Boolean goForward, Boolean caseSensitive);
|
||||
void SetBackgroundOpacity(Double opacity);
|
||||
Microsoft.Terminal.Core.Color BackgroundColor();
|
||||
Microsoft.Terminal.Core.Color BackgroundColor { get; };
|
||||
|
||||
Boolean HasSelection();
|
||||
Boolean HasSelection { get; };
|
||||
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
|
||||
|
||||
String GetHoveredUriText();
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> GetHoveredCell();
|
||||
String HoveredUriText { get; };
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
|
||||
|
||||
void Close();
|
||||
void BlinkCursor();
|
||||
Boolean IsInReadOnlyMode();
|
||||
Boolean IsInReadOnlyMode { get; };
|
||||
Boolean CursorOn;
|
||||
void EnablePainting();
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
}
|
||||
|
||||
Control::ControlCore ControlInteractivity::GetCore()
|
||||
Control::ControlCore ControlInteractivity::Core()
|
||||
{
|
||||
return *_core;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
void LostFocus();
|
||||
void UpdateSettings();
|
||||
void Initialize();
|
||||
Control::ControlCore GetCore();
|
||||
Control::ControlCore Core();
|
||||
|
||||
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
|
@ -87,12 +87,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
|
||||
|
||||
private:
|
||||
// NOTE: _uiaEngine must be ordered before _core.
|
||||
//
|
||||
// ControlCore::AttachUiaEngine receives a IRenderEngine as a raw pointer, which we own.
|
||||
// We must ensure that we first destroy the ControlCore before the UiaEngine instance
|
||||
// in order to safely resolve this unsafe pointer dependency. Otherwise a deallocated
|
||||
// IRenderEngine is accessed when ControlCore calls Renderer::TriggerTeardown.
|
||||
// (C++ class members are destroyed in reverse order.)
|
||||
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
|
||||
|
||||
winrt::com_ptr<ControlCore> _core{ nullptr };
|
||||
unsigned int _rowsToScroll;
|
||||
double _internalScrollbarPosition{ 0.0 };
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
|
||||
|
||||
// If this is set, then we assume we are in the middle of panning the
|
||||
// viewport via touch input.
|
||||
std::optional<til::point> _touchAnchor;
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Microsoft.Terminal.Control
|
|||
ControlInteractivity(IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
ControlCore GetCore();
|
||||
ControlCore Core { get; };
|
||||
void UpdateSettings();
|
||||
void Initialize();
|
||||
void GainFocus();
|
||||
|
|
|
@ -45,71 +45,48 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
}
|
||||
|
||||
// Method Description:
|
||||
// - Signals the ui automation client that the terminal's selection has changed and should be updated
|
||||
// - Signals the ui automation client that the terminal's selection has
|
||||
// changed and should be updated
|
||||
// - We will raise a new event, for out embedding control to be able to
|
||||
// raise the event. AutomationPeer by itself doesn't hook up to the
|
||||
// eventing mechanism, we need the FrameworkAutomationPeer to do that.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void InteractivityAutomationPeer::SignalSelectionChanged()
|
||||
{
|
||||
UiaTracing::Signal::SelectionChanged();
|
||||
|
||||
// TODO:projects/5#card-50760282
|
||||
// We seemingly got a Dispatcher() for free when we said we extended
|
||||
// Windows.UI.Automation.Peers.AutomationPeer. This is suspect to me.
|
||||
// This probably won't work when out-of-proc from the WinUI layer.
|
||||
|
||||
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
|
||||
// The event that is raised when the text selection is modified.
|
||||
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged);
|
||||
});
|
||||
_SelectionChangedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Signals the ui automation client that the terminal's output has changed and should be updated
|
||||
// - Signals the ui automation client that the terminal's output has changed
|
||||
// and should be updated
|
||||
// - We will raise a new event, for out embedding control to be able to
|
||||
// raise the event. AutomationPeer by itself doesn't hook up to the
|
||||
// eventing mechanism, we need the FrameworkAutomationPeer to do that.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void InteractivityAutomationPeer::SignalTextChanged()
|
||||
{
|
||||
UiaTracing::Signal::TextChanged();
|
||||
|
||||
// TODO:projects/5#card-50760282
|
||||
// We seemingly got a Dispatcher() for free when we said we extended
|
||||
// Windows.UI.Automation.Peers.AutomationPeer. This is suspect to me.
|
||||
// This probably won't work when out-of-proc from the WinUI layer.
|
||||
|
||||
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
|
||||
// The event that is raised when textual content is modified.
|
||||
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextChanged);
|
||||
});
|
||||
_TextChangedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Signals the ui automation client that the cursor's state has changed and should be updated
|
||||
// - Signals the ui automation client that the cursor's state has changed
|
||||
// and should be updated
|
||||
// - We will raise a new event, for out embedding control to be able to
|
||||
// raise the event. AutomationPeer by itself doesn't hook up to the
|
||||
// eventing mechanism, we need the FrameworkAutomationPeer to do that.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void InteractivityAutomationPeer::SignalCursorChanged()
|
||||
{
|
||||
UiaTracing::Signal::CursorChanged();
|
||||
|
||||
// TODO:projects/5#card-50760282
|
||||
// We seemingly got a Dispatcher() for free when we said we extended
|
||||
// Windows.UI.Automation.Peers.AutomationPeer. This is suspect to me.
|
||||
// This probably won't work when out-of-proc from the WinUI layer.
|
||||
|
||||
Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [&]() {
|
||||
// The event that is raised when the text was changed in an edit control.
|
||||
// Do NOT fire a TextEditTextChanged. Generally, an app on the other side
|
||||
// will expect more information. Though you can dispatch that event
|
||||
// on its own, it may result in a nullptr exception on the other side
|
||||
// because no additional information was provided. Crashing the screen
|
||||
// reader.
|
||||
RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged);
|
||||
});
|
||||
_CursorChangedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
#pragma region ITextProvider
|
||||
|
@ -134,8 +111,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
THROW_IF_FAILED(_uiaProvider->RangeFromChild(/* IRawElementProviderSimple */ nullptr,
|
||||
&returnVal));
|
||||
|
||||
auto parentProvider = this->ProviderFromPeer(*this);
|
||||
auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
}
|
||||
|
||||
|
@ -144,8 +121,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
UIA::ITextRangeProvider* returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->RangeFromPoint({ screenLocation.X, screenLocation.Y }, &returnVal));
|
||||
|
||||
auto parentProvider = this->ProviderFromPeer(*this);
|
||||
auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
}
|
||||
|
||||
|
@ -154,8 +131,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
UIA::ITextRangeProvider* returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->get_DocumentRange(&returnVal));
|
||||
|
||||
auto parentProvider = this->ProviderFromPeer(*this);
|
||||
auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
}
|
||||
|
||||
|
@ -171,7 +148,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
#pragma region IControlAccessibilityInfo
|
||||
COORD InteractivityAutomationPeer::GetFontSize() const
|
||||
{
|
||||
return til::size{ til::math::rounding, _interactivity->GetCore().FontSize() };
|
||||
return til::size{ til::math::rounding, _interactivity->Core().FontSize() };
|
||||
}
|
||||
|
||||
RECT InteractivityAutomationPeer::GetBounds() const
|
||||
|
|
|
@ -16,7 +16,7 @@ Abstract:
|
|||
ControlInteractivity is totally oblivious to the UI tree that might be hosting
|
||||
it. So this class implements the actual text pattern for the buffer, because
|
||||
it has access to the buffer. TermControlAutomationPeer can then call the
|
||||
methods on this class to expose the implemeentation in the actual UI tree.
|
||||
methods on this class to expose the implementation in the actual UI tree.
|
||||
|
||||
Author(s):
|
||||
- Mike Griese (migrie), May 2021
|
||||
|
@ -69,6 +69,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
virtual HRESULT GetHostUiaProvider(IRawElementProviderSimple** provider) override;
|
||||
#pragma endregion
|
||||
|
||||
TYPED_EVENT(SelectionChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(TextChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(CursorChanged, IInspectable, IInspectable);
|
||||
|
||||
private:
|
||||
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
|
||||
winrt::Microsoft::Terminal::Control::implementation::ControlInteractivity* _interactivity;
|
||||
|
|
|
@ -10,5 +10,9 @@ namespace Microsoft.Terminal.Control
|
|||
|
||||
void SetControlBounds(Windows.Foundation.Rect bounds);
|
||||
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> SelectionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TextChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> CursorChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
InitializeComponent();
|
||||
|
||||
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
|
||||
_core = _interactivity.GetCore();
|
||||
_core = _interactivity.Core();
|
||||
|
||||
// Use a manual revoker on the output event, so we can immediately stop
|
||||
// worrying about it on destruction.
|
||||
|
@ -570,7 +570,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
const HANDLE chainHandle = reinterpret_cast<HANDLE>(control->_core.GetSwapChainHandle());
|
||||
const HANDLE chainHandle = reinterpret_cast<HANDLE>(control->_core.SwapChainHandle());
|
||||
_AttachDxgiSwapChainToXaml(chainHandle);
|
||||
}
|
||||
}
|
||||
|
@ -658,7 +658,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
}
|
||||
_interactivity.Initialize();
|
||||
|
||||
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.GetSwapChainHandle()));
|
||||
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
|
||||
|
||||
// Tell the DX Engine to notify us when the swap chain changes. We do
|
||||
// this after we initially set the swapchain so as to avoid unnecessary
|
||||
|
@ -2386,10 +2386,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
co_await resume_foreground(Dispatcher());
|
||||
if (auto self{ weakThis.get() })
|
||||
{
|
||||
auto lastHoveredCell = _core.GetHoveredCell();
|
||||
auto lastHoveredCell = _core.HoveredCell();
|
||||
if (lastHoveredCell)
|
||||
{
|
||||
const auto uriText = _core.GetHoveredUriText();
|
||||
const auto uriText = _core.HoveredUriText();
|
||||
if (!uriText.empty())
|
||||
{
|
||||
// Update the tooltip with the URI
|
||||
|
|
|
@ -134,17 +134,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
Control::ControlCore _core{ nullptr };
|
||||
Control::ControlInteractivity _interactivity{ nullptr };
|
||||
|
||||
bool _initializedTerminal;
|
||||
|
||||
winrt::com_ptr<SearchBoxControl> _searchBox;
|
||||
|
||||
IControlSettings _settings;
|
||||
bool _focused;
|
||||
std::atomic<bool> _closing;
|
||||
bool _focused;
|
||||
bool _initializedTerminal;
|
||||
|
||||
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
|
||||
std::shared_ptr<ThrottledFunc<>> _updatePatternLocations;
|
||||
|
||||
std::shared_ptr<ThrottledFunc<>> _playWarningBell;
|
||||
|
||||
struct ScrollBarUpdate
|
||||
|
|
|
@ -34,9 +34,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
Control::InteractivityAutomationPeer impl) :
|
||||
TermControlAutomationPeerT<TermControlAutomationPeer>(*owner), // pass owner to FrameworkElementAutomationPeer
|
||||
_termControl{ owner },
|
||||
_implementation{ impl }
|
||||
_contentAutomationPeer{ impl }
|
||||
{
|
||||
UpdateControlBounds();
|
||||
|
||||
// Listen for UIA signalling events from the implementation. We need to
|
||||
// be the one to actually raise these automation events, so they go
|
||||
// through the UI tree correctly.
|
||||
_contentAutomationPeer.SelectionChanged([this](auto&&, auto&&) { SignalSelectionChanged(); });
|
||||
_contentAutomationPeer.TextChanged([this](auto&&, auto&&) { SignalTextChanged(); });
|
||||
_contentAutomationPeer.CursorChanged([this](auto&&, auto&&) { SignalCursorChanged(); });
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
|
@ -52,11 +59,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
// FrameworkElementAutomationPeer has this great GetBoundingRectangle
|
||||
// method that's seemingly impossible to recreate just from the
|
||||
// UserControl itself. Weird. But we can use it handily here!
|
||||
_implementation.SetControlBounds(GetBoundingRectangle());
|
||||
_contentAutomationPeer.SetControlBounds(GetBoundingRectangle());
|
||||
}
|
||||
void TermControlAutomationPeer::SetControlPadding(const Core::Padding padding)
|
||||
{
|
||||
_implementation.SetControlPadding(padding);
|
||||
_contentAutomationPeer.SetControlPadding(padding);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -165,32 +172,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
#pragma region ITextProvider
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
|
||||
{
|
||||
return _implementation.GetSelection();
|
||||
return _contentAutomationPeer.GetSelection();
|
||||
}
|
||||
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
|
||||
{
|
||||
return _implementation.GetVisibleRanges();
|
||||
return _contentAutomationPeer.GetVisibleRanges();
|
||||
}
|
||||
|
||||
XamlAutomation::ITextRangeProvider TermControlAutomationPeer::RangeFromChild(XamlAutomation::IRawElementProviderSimple childElement)
|
||||
{
|
||||
return _implementation.RangeFromChild(childElement);
|
||||
return _contentAutomationPeer.RangeFromChild(childElement);
|
||||
}
|
||||
|
||||
XamlAutomation::ITextRangeProvider TermControlAutomationPeer::RangeFromPoint(Windows::Foundation::Point screenLocation)
|
||||
{
|
||||
return _implementation.RangeFromPoint(screenLocation);
|
||||
return _contentAutomationPeer.RangeFromPoint(screenLocation);
|
||||
}
|
||||
|
||||
XamlAutomation::ITextRangeProvider TermControlAutomationPeer::DocumentRange()
|
||||
{
|
||||
return _implementation.DocumentRange();
|
||||
return _contentAutomationPeer.DocumentRange();
|
||||
}
|
||||
|
||||
XamlAutomation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
|
||||
{
|
||||
return _implementation.SupportedTextSelection();
|
||||
return _contentAutomationPeer.SupportedTextSelection();
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
|
||||
private:
|
||||
winrt::Microsoft::Terminal::Control::implementation::TermControl* _termControl;
|
||||
Control::InteractivityAutomationPeer _implementation;
|
||||
Control::InteractivityAutomationPeer _contentAutomationPeer;
|
||||
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges);
|
||||
};
|
||||
|
|
|
@ -25,8 +25,8 @@ namespace Microsoft.Terminal.Core
|
|||
};
|
||||
|
||||
// TerminalCore declares its own Color struct to avoid depending on
|
||||
// Windows.UI. Windows.Foundation.POint also exists, but it's composedof
|
||||
// floating-point coordinates,whenwe almost always need integer coordinates.
|
||||
// Windows.UI. Windows.Foundation.Point also exists, but it's composed of
|
||||
// floating-point coordinates,when we almost always need integer coordinates.
|
||||
// It is supported by til::point for conversions in and out of WinRT land.
|
||||
struct Point
|
||||
{
|
||||
|
|
|
@ -260,8 +260,8 @@ namespace ControlUnitTests
|
|||
|
||||
// For this test, don't use any modifiers
|
||||
const auto modifiers = ControlKeyStates();
|
||||
const TerminalInput::MouseButtonState leftMouseDown{ true, false, false };
|
||||
const TerminalInput::MouseButtonState noMouseDown{ false, false, false };
|
||||
const Control::MouseButtonState leftMouseDown{ true, false, false };
|
||||
const Control::MouseButtonState noMouseDown{ false, false, false };
|
||||
|
||||
const til::size fontSize{ 9, 21 };
|
||||
|
||||
|
@ -358,8 +358,8 @@ namespace ControlUnitTests
|
|||
|
||||
// For this test, don't use any modifiers
|
||||
const auto modifiers = ControlKeyStates();
|
||||
const TerminalInput::MouseButtonState leftMouseDown{ true, false, false };
|
||||
const TerminalInput::MouseButtonState noMouseDown{ false, false, false };
|
||||
const Control::MouseButtonState leftMouseDown{ true, false, false };
|
||||
const Control::MouseButtonState noMouseDown{ false, false, false };
|
||||
|
||||
const til::size fontSize{ 9, 21 };
|
||||
|
||||
|
@ -444,7 +444,7 @@ namespace ControlUnitTests
|
|||
|
||||
const int delta = WHEEL_DELTA / 5;
|
||||
const til::point mousePos{ 0, 0 };
|
||||
TerminalInput::MouseButtonState state{ false, false, false };
|
||||
Control::MouseButtonState state{ false, false, false };
|
||||
|
||||
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
|
||||
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
||||
|
@ -508,8 +508,8 @@ namespace ControlUnitTests
|
|||
|
||||
// For this test, don't use any modifiers
|
||||
const auto modifiers = ControlKeyStates();
|
||||
const TerminalInput::MouseButtonState leftMouseDown{ true, false, false };
|
||||
const TerminalInput::MouseButtonState noMouseDown{ false, false, false };
|
||||
const Control::MouseButtonState leftMouseDown{ true, false, false };
|
||||
const Control::MouseButtonState noMouseDown{ false, false, false };
|
||||
|
||||
const til::size fontSize{ 9, 21 };
|
||||
|
||||
|
|
100
src/host/ft_host/API_MultipleInflightMessageTests.cpp
Normal file
100
src/host/ft_host/API_MultipleInflightMessageTests.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
class MultipleInflightMessageTests
|
||||
{
|
||||
BEGIN_TEST_CLASS(MultipleInflightMessageTests)
|
||||
END_TEST_CLASS()
|
||||
|
||||
// This test is intended to make sure that we do not regress after the _handlePostCharInputLoop fix in OpenConsole:c0ab9cb5b
|
||||
TEST_METHOD(WriteWhileReadingInputCrash)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") // Don't pollute other tests by isolating our potential crash and buffer resizing to this test.
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
const auto inputHandle = GetStdInputHandle();
|
||||
const auto outputHandle = GetStdOutputHandle();
|
||||
|
||||
DWORD originalConsoleMode{};
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(inputHandle, &originalConsoleMode));
|
||||
auto restoreMode{ wil::scope_exit([=]() {
|
||||
SetConsoleMode(inputHandle, originalConsoleMode);
|
||||
}) };
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(inputHandle, ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT));
|
||||
|
||||
// Prime the console with some fake input records.
|
||||
const std::array<INPUT_RECORD, 4> inputRecords{
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ TRUE, 1, 'h', 0, L'h', 0 } },
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ FALSE, 1, 'h', 0, L'h', 0 } },
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ TRUE, 1, 'i', 0, L'i', 0 } },
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ FALSE, 1, 'i', 0, L'i', 0 } },
|
||||
};
|
||||
DWORD written{};
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInput(inputHandle, inputRecords.data(), static_cast<DWORD>(inputRecords.size()), &written));
|
||||
VERIFY_ARE_EQUAL(inputRecords.size(), written);
|
||||
|
||||
// !!! LOAD BEARING !!!
|
||||
// This buffer needs to be large enough to force API_MSG to heap allocate (!)
|
||||
std::array<wchar_t, 129> buffer;
|
||||
DWORD read{};
|
||||
|
||||
bool readerThreadLaunched{ false };
|
||||
std::condition_variable readerThreadLaunchCV;
|
||||
std::mutex readerThreadLaunchMutex;
|
||||
|
||||
std::unique_lock lock{ readerThreadLaunchMutex };
|
||||
|
||||
std::thread readerThread{ [&]() {
|
||||
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
||||
{
|
||||
std::scoped_lock lock{ readerThreadLaunchMutex };
|
||||
readerThreadLaunched = true;
|
||||
}
|
||||
readerThreadLaunchCV.notify_all();
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleW(inputHandle, buffer.data(), static_cast<DWORD>(buffer.size()), &read, nullptr));
|
||||
} };
|
||||
|
||||
// CV wait should allow readerThread to progress
|
||||
readerThreadLaunchCV.wait(lock, [&]() { return readerThreadLaunched; });
|
||||
// should not progress until readerThreadLaunched is set.
|
||||
Sleep(50); // Yeah, it's not science.
|
||||
|
||||
std::thread writerThread{ [&]() {
|
||||
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
||||
DWORD temp{};
|
||||
// !!! LOAD BEARING !!!
|
||||
// This buffer must be large enough to trigger a *re-allocation* in the API message handler.
|
||||
std::array<wchar_t, 4096> anEvenLargerBuffer;
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterW(outputHandle, anEvenLargerBuffer.data(), static_cast<DWORD>(anEvenLargerBuffer.size()), COORD{ 1, 1 }, &temp)); // has payload (output buffer)
|
||||
VERIFY_ARE_EQUAL(4096u, temp);
|
||||
|
||||
const std::array<INPUT_RECORD, 2> inputRecords{
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ TRUE, 1, '1', 0, L'!', SHIFT_PRESSED } },
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ FALSE, 1, '1', 0, L'!', SHIFT_PRESSED } },
|
||||
};
|
||||
DWORD written{};
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(inputHandle, inputRecords.data(), static_cast<DWORD>(inputRecords.size()), &written));
|
||||
VERIFY_ARE_EQUAL(inputRecords.size(), written);
|
||||
|
||||
// !!! LOAD BEARING !!!
|
||||
// It is (apparently) important that this come in two different writes.
|
||||
|
||||
const std::array<INPUT_RECORD, 2> inputRecords2{
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ TRUE, 1, VK_RETURN, 0, L'\r', 0 } },
|
||||
INPUT_RECORD{ KEY_EVENT, KEY_EVENT_RECORD{ FALSE, 1, VK_RETURN, 0, L'\r', 0 } },
|
||||
};
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(inputHandle, inputRecords2.data(), static_cast<DWORD>(inputRecords2.size()), &written));
|
||||
VERIFY_ARE_EQUAL(inputRecords2.size(), written);
|
||||
} };
|
||||
|
||||
writerThread.join();
|
||||
readerThread.join();
|
||||
|
||||
VERIFY_ARE_EQUAL(L"hi!\r"sv, (std::wstring_view{ buffer.data(), read }));
|
||||
}
|
||||
};
|
|
@ -19,6 +19,7 @@
|
|||
<ClCompile Include="API_FontTests.cpp" />
|
||||
<ClCompile Include="API_InputTests.cpp" />
|
||||
<ClCompile Include="API_ModeTests.cpp" />
|
||||
<ClCompile Include="API_MultipleInflightMessageTests.cpp" />
|
||||
<ClCompile Include="API_OutputTests.cpp" />
|
||||
<ClCompile Include="API_RgbColorTests.cpp" />
|
||||
<ClCompile Include="API_TitleTests.cpp" />
|
||||
|
|
|
@ -84,6 +84,9 @@
|
|||
<ClCompile Include="API_FillOutputTests.cpp">
|
||||
<Filter>Source Files\API</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="API_MultipleInflightMessageTests.cpp">
|
||||
<Filter>Source Files\API</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common.hpp">
|
||||
|
|
|
@ -32,6 +32,7 @@ SOURCES = \
|
|||
API_FontTests.cpp \
|
||||
API_InputTests.cpp \
|
||||
API_ModeTests.cpp \
|
||||
API_MultipleInflightMessageTests.cpp \
|
||||
API_OutputTests.cpp \
|
||||
API_RgbColorTests.cpp \
|
||||
API_TitleTests.cpp \
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
],
|
||||
"RemoteFiles": [ ],
|
||||
"Packages": [ "Microsoft.Console.Tools.Nihilist" ]
|
||||
"Packages": [ "Microsoft.Console.Tools.Nihilist", "microsoft-windows-test-testnetv5-0" ]
|
||||
},
|
||||
"Logs": [ ],
|
||||
"Plugins": [ ],
|
||||
|
|
|
@ -36,6 +36,7 @@ public:
|
|||
ReadData& operator=(const ReadData&) & = delete;
|
||||
ReadData& operator=(ReadData&&) & = delete;
|
||||
|
||||
virtual void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) = 0;
|
||||
virtual bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
|
|
|
@ -1197,3 +1197,12 @@ void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline
|
|||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void COOKED_READ_DATA::MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer)
|
||||
{
|
||||
// See the comment in WaitBlock.cpp for more information.
|
||||
if (_userBuffer == reinterpret_cast<const wchar_t*>(oldBuffer))
|
||||
{
|
||||
_userBuffer = reinterpret_cast<wchar_t*>(newBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ public:
|
|||
|
||||
bool AtEol() const noexcept;
|
||||
|
||||
void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override;
|
||||
|
||||
bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
|
|
|
@ -190,3 +190,8 @@ bool DirectReadData::Notify(const WaitTerminationReason TerminationReason,
|
|||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void DirectReadData::MigrateUserBuffersOnTransitionToBackgroundWait(const void* /*oldBuffer*/, void* /*newBuffer*/)
|
||||
{
|
||||
// Direct read doesn't hold API message buffers
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ public:
|
|||
|
||||
~DirectReadData() override;
|
||||
|
||||
void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override;
|
||||
bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
|
|
|
@ -218,3 +218,12 @@ bool RAW_READ_DATA::Notify(const WaitTerminationReason TerminationReason,
|
|||
}
|
||||
return RetVal;
|
||||
}
|
||||
|
||||
void RAW_READ_DATA::MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer)
|
||||
{
|
||||
// See the comment in WaitBlock.cpp for more information.
|
||||
if (_BufPtr == reinterpret_cast<const wchar_t*>(oldBuffer))
|
||||
{
|
||||
_BufPtr = reinterpret_cast<wchar_t*>(newBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
|
||||
RAW_READ_DATA(RAW_READ_DATA&&) = default;
|
||||
|
||||
void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override;
|
||||
bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
|
|
|
@ -189,3 +189,8 @@ bool WriteData::Notify(const WaitTerminationReason TerminationReason,
|
|||
*pReplyStatus = Status;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteData::MigrateUserBuffersOnTransitionToBackgroundWait(const void* /*oldBuffer*/, void* /*newBuffer*/)
|
||||
{
|
||||
// WriteData does not hold API message buffers across a wait
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public:
|
|||
|
||||
void SetUtf8ConsumedCharacters(const size_t cchUtf8Consumed);
|
||||
|
||||
void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) override;
|
||||
bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
|
|
|
@ -27,7 +27,8 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<DxFontRenderData*> const fontRe
|
|||
_runs{},
|
||||
_breakpoints{},
|
||||
_runIndex{ 0 },
|
||||
_width{ gsl::narrow_cast<size_t>(fontRenderData->GlyphCell().width()) }
|
||||
_width{ gsl::narrow_cast<size_t>(fontRenderData->GlyphCell().width()) },
|
||||
_isEntireTextSimple{ false }
|
||||
{
|
||||
_localeName.resize(gsl::narrow_cast<size_t>(fontRenderData->DefaultTextFormat()->GetLocaleNameLength()) + 1); // +1 for null
|
||||
THROW_IF_FAILED(fontRenderData->DefaultTextFormat()->GetLocaleName(_localeName.data(), gsl::narrow<UINT32>(_localeName.size())));
|
||||
|
@ -45,6 +46,7 @@ try
|
|||
_runs.clear();
|
||||
_breakpoints.clear();
|
||||
_runIndex = 0;
|
||||
_isEntireTextSimple = false;
|
||||
_textClusterColumns.clear();
|
||||
_text.clear();
|
||||
_glyphScaleCorrections.clear();
|
||||
|
@ -101,6 +103,7 @@ CATCH_RETURN()
|
|||
_formatInUse = _fontRenderData->DefaultTextFormat().Get();
|
||||
_fontInUse = _fontRenderData->DefaultFontFace().Get();
|
||||
|
||||
RETURN_IF_FAILED(_AnalyzeTextComplexity());
|
||||
RETURN_IF_FAILED(_AnalyzeRuns());
|
||||
RETURN_IF_FAILED(_ShapeGlyphRuns());
|
||||
|
||||
|
@ -135,6 +138,7 @@ CATCH_RETURN()
|
|||
_formatInUse = drawingContext->useItalicFont ? _fontRenderData->ItalicTextFormat().Get() : _fontRenderData->DefaultTextFormat().Get();
|
||||
_fontInUse = drawingContext->useItalicFont ? _fontRenderData->ItalicFontFace().Get() : _fontRenderData->DefaultFontFace().Get();
|
||||
|
||||
RETURN_IF_FAILED(_AnalyzeTextComplexity());
|
||||
RETURN_IF_FAILED(_AnalyzeRuns());
|
||||
RETURN_IF_FAILED(_ShapeGlyphRuns());
|
||||
RETURN_IF_FAILED(_CorrectGlyphRuns());
|
||||
|
@ -150,9 +154,8 @@ CATCH_RETURN()
|
|||
|
||||
// Routine Description:
|
||||
// - Uses the internal text information and the analyzers/font information from construction
|
||||
// to determine the complexity of the text. During the process we break the text into initial
|
||||
// runs based on their complexity. This allows us to further optimize the layout process
|
||||
// of simple runs.
|
||||
// to determine the complexity of the text. If the text is determined to be entirely simple,
|
||||
// we'll have more chances to optimize the layout process.
|
||||
// Arguments:
|
||||
// - <none> - Uses internal state
|
||||
// Return Value:
|
||||
|
@ -167,30 +170,21 @@ CATCH_RETURN()
|
|||
UINT32 uiLengthRead = 0;
|
||||
|
||||
// Start from the beginning.
|
||||
UINT32 pos = 0;
|
||||
const UINT32 glyphStart = 0;
|
||||
|
||||
_glyphIndices.resize(textLength);
|
||||
|
||||
while (pos < textLength)
|
||||
{
|
||||
const HRESULT hr = _fontRenderData->Analyzer()->GetTextComplexity(
|
||||
&_text.at(pos),
|
||||
textLength,
|
||||
_fontInUse,
|
||||
&isTextSimple,
|
||||
&uiLengthRead,
|
||||
&_glyphIndices.at(pos));
|
||||
const HRESULT hr = _fontRenderData->Analyzer()->GetTextComplexity(
|
||||
_text.c_str(),
|
||||
textLength,
|
||||
_fontInUse,
|
||||
&isTextSimple,
|
||||
&uiLengthRead,
|
||||
&_glyphIndices.at(glyphStart));
|
||||
|
||||
RETURN_IF_FAILED(hr);
|
||||
_SetCurrentRun(pos);
|
||||
_SplitCurrentRun(pos);
|
||||
pos += std::max(uiLengthRead, 1u);
|
||||
while (uiLengthRead > 0)
|
||||
{
|
||||
auto& run = _FetchNextRun(uiLengthRead);
|
||||
run.isTextSimple = isTextSimple;
|
||||
}
|
||||
}
|
||||
RETURN_IF_FAILED(hr);
|
||||
|
||||
_isEntireTextSimple = isTextSimple && uiLengthRead == textLength;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
return S_OK;
|
||||
|
@ -224,26 +218,15 @@ CATCH_RETURN()
|
|||
// Allocate enough room to have one breakpoint per code unit.
|
||||
_breakpoints.resize(_text.size());
|
||||
|
||||
RETURN_IF_FAILED(_AnalyzeTextComplexity());
|
||||
|
||||
std::vector<std::pair<UINT32, UINT32>> complexRanges;
|
||||
for (auto& run : _runs)
|
||||
{
|
||||
if (!run.isTextSimple)
|
||||
{
|
||||
complexRanges.push_back(std::make_pair(run.textStart, run.textLength));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& range : complexRanges)
|
||||
if (!_isEntireTextSimple)
|
||||
{
|
||||
// Call each of the analyzers in sequence, recording their results.
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeLineBreakpoints(this, range.first, range.second, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeBidi(this, range.first, range.second, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeScript(this, range.first, range.second, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeNumberSubstitution(this, range.first, range.second, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeLineBreakpoints(this, 0, textLength, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeBidi(this, 0, textLength, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeScript(this, 0, textLength, this));
|
||||
RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeNumberSubstitution(this, 0, textLength, this));
|
||||
// Perform our custom font fallback analyzer that mimics the pattern of the real analyzers.
|
||||
RETURN_IF_FAILED(_AnalyzeFontFallback(this, range.first, range.second));
|
||||
RETURN_IF_FAILED(_AnalyzeFontFallback(this, 0, textLength));
|
||||
}
|
||||
|
||||
// Ensure that a font face is attached to every run
|
||||
|
@ -283,7 +266,6 @@ CATCH_RETURN()
|
|||
_glyphOffsets.resize(estimatedGlyphCount);
|
||||
_glyphAdvances.resize(estimatedGlyphCount);
|
||||
_glyphClusters.resize(textLength);
|
||||
_glyphDesignUnitAdvances.resize(textLength);
|
||||
|
||||
UINT32 glyphStart = 0;
|
||||
|
||||
|
@ -357,7 +339,7 @@ CATCH_RETURN()
|
|||
_glyphIndices.resize(totalGlyphsArrayCount);
|
||||
}
|
||||
|
||||
if (run.isTextSimple)
|
||||
if (_isEntireTextSimple)
|
||||
{
|
||||
// When the entire text is simple, we can skip GetGlyphs and directly retrieve glyph indices and
|
||||
// advances(in font design unit). With the help of font metrics, we can calculate the actual glyph
|
||||
|
@ -366,6 +348,10 @@ CATCH_RETURN()
|
|||
DWRITE_FONT_METRICS1 metrics;
|
||||
run.fontFace->GetMetrics(&metrics);
|
||||
|
||||
// With simple text, there's only one run. The actual glyph count is the same as textLength.
|
||||
_glyphDesignUnitAdvances.resize(textLength);
|
||||
_glyphAdvances.resize(textLength);
|
||||
|
||||
USHORT designUnitsPerEm = metrics.designUnitsPerEm;
|
||||
|
||||
RETURN_IF_FAILED(_fontInUse->GetDesignGlyphAdvances(
|
||||
|
@ -374,14 +360,14 @@ CATCH_RETURN()
|
|||
&_glyphDesignUnitAdvances.at(glyphStart),
|
||||
run.isSideways));
|
||||
|
||||
for (UINT32 i = glyphStart; i < glyphStart + textLength; i++)
|
||||
for (size_t i = glyphStart; i < _glyphAdvances.size(); i++)
|
||||
{
|
||||
_glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _formatInUse->GetFontSize() * run.fontScale;
|
||||
}
|
||||
|
||||
// Set all the clusters as sequential. In a simple run, we're going 1 to 1.
|
||||
// Fill the clusters sequentially from 0 to N-1.
|
||||
std::iota(_glyphClusters.begin() + glyphStart, _glyphClusters.begin() + glyphStart + textLength, gsl::narrow_cast<unsigned short>(0));
|
||||
std::iota(_glyphClusters.begin(), _glyphClusters.end(), gsl::narrow_cast<unsigned short>(0));
|
||||
|
||||
run.glyphCount = textLength;
|
||||
glyphStart += textLength;
|
||||
|
@ -486,6 +472,12 @@ CATCH_RETURN()
|
|||
{
|
||||
try
|
||||
{
|
||||
// For simple text, there is no need to correct runs.
|
||||
if (_isEntireTextSimple)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Correct each run separately. This is needed whenever script, locale,
|
||||
// or reading direction changes.
|
||||
for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex)
|
||||
|
@ -625,11 +617,6 @@ try
|
|||
return S_FALSE; // Nothing to do..
|
||||
}
|
||||
|
||||
if (run.isTextSimple)
|
||||
{
|
||||
return S_OK; // No need to correct run.
|
||||
}
|
||||
|
||||
// We're going to walk through and check for advances that don't match the space that we expect to give out.
|
||||
|
||||
// Glyph Indices represents the number inside the selected font where the glyph image/paths are found.
|
||||
|
|
|
@ -76,7 +76,6 @@ namespace Microsoft::Console::Render
|
|||
glyphCount(),
|
||||
bidiLevel(),
|
||||
script(),
|
||||
isTextSimple(),
|
||||
isNumberSubstituted(),
|
||||
isSideways(),
|
||||
fontFace{ nullptr },
|
||||
|
@ -91,7 +90,6 @@ namespace Microsoft::Console::Render
|
|||
UINT32 glyphCount; // number of glyphs associated with this run of text
|
||||
DWRITE_SCRIPT_ANALYSIS script;
|
||||
UINT8 bidiLevel;
|
||||
bool isTextSimple;
|
||||
bool isNumberSubstituted;
|
||||
bool isSideways;
|
||||
::Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace;
|
||||
|
@ -178,6 +176,10 @@ namespace Microsoft::Console::Render
|
|||
UINT32 _runIndex;
|
||||
|
||||
// Glyph shaping results
|
||||
|
||||
// Whether the entire text is determined to be simple and does not require full script shaping.
|
||||
bool _isEntireTextSimple;
|
||||
|
||||
std::vector<DWRITE_GLYPH_OFFSET> _glyphOffsets;
|
||||
|
||||
// Clusters are complicated. They're in respect to each individual run.
|
||||
|
|
|
@ -194,3 +194,22 @@ void _CONSOLE_API_MSG::SetReplyInformation(const ULONG_PTR pInformation)
|
|||
{
|
||||
Complete.IoStatus.Information = pInformation;
|
||||
}
|
||||
|
||||
void _CONSOLE_API_MSG::UpdateUserBufferPointers()
|
||||
{
|
||||
// There are some instances where an API message may get copied.
|
||||
// Because it is infeasible to write a copy constructor for this class
|
||||
// without rewriting large swaths of conhost (because of the unnamed union)
|
||||
// we have chosen to introduce a "post-copy" step.
|
||||
// This makes sure the buffers in State are in sync with the actual
|
||||
// buffers in the object.
|
||||
if (State.InputBuffer)
|
||||
{
|
||||
State.InputBuffer = _inputBuffer.data();
|
||||
}
|
||||
|
||||
if (State.OutputBuffer)
|
||||
{
|
||||
State.OutputBuffer = _outputBuffer.data();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,13 @@ public:
|
|||
void SetReplyStatus(const NTSTATUS Status);
|
||||
void SetReplyInformation(const ULONG_PTR pInformation);
|
||||
|
||||
// MSFT-33127449, GH#9692
|
||||
// We are not writing a copy constructor for this class
|
||||
// so as to scope a fix as narrowly as possible to the
|
||||
// "invalid user buffer" crash.
|
||||
// TODO GH#10076: remove this.
|
||||
void UpdateUserBufferPointers();
|
||||
|
||||
// DO NOT PUT MORE FIELDS DOWN HERE.
|
||||
// The tail end of this structure will have a console driver packet
|
||||
// copied over it and it will overwrite any fields declared here.
|
||||
|
|
|
@ -39,6 +39,8 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void MigrateUserBuffersOnTransitionToBackgroundWait(const void* oldBuffer, void* newBuffer) = 0;
|
||||
|
||||
virtual bool Notify(const WaitTerminationReason TerminationReason,
|
||||
const bool fIsUnicode,
|
||||
_Out_ NTSTATUS* const pReplyStatus,
|
||||
|
|
|
@ -32,6 +32,44 @@ ConsoleWaitBlock::ConsoleWaitBlock(_In_ ConsoleWaitQueue* const pProcessQueue,
|
|||
{
|
||||
_WaitReplyMessage = *pWaitReplyMessage;
|
||||
|
||||
// MSFT-33127449, GH#9692
|
||||
// Until there's a "Wait", there's only one API message inflight at a time. In our
|
||||
// quest for performance, we put that single API message in charge of its own
|
||||
// buffer management- instead of allocating buffers on the heap and deleting them
|
||||
// later (storing pointers to them at the far corners of the earth), it would
|
||||
// instead allocate them from small internal pools (if possible) and only heap
|
||||
// allocate (transparently) if necessary. The pointers flung to the corners of the
|
||||
// earth would be pointers (1) back into the API_MSG or (2) to a heap block owned
|
||||
// by boost::small_vector.
|
||||
//
|
||||
// It took us months to realize that those bare pointers were being held by
|
||||
// COOKED_READ and RAW_READ and not actually being updated when the API message was
|
||||
// _copied_ as it was shuffled off to the background to become a "Wait" message.
|
||||
//
|
||||
// It turns out that it's trivially possible to crash the console by sending two
|
||||
// API calls -- one that waits and one that completes immediately -- when the
|
||||
// waiting message or the "wait completer" has a bunch of dangling pointers in it.
|
||||
// Oops.
|
||||
//
|
||||
// Here, we fix up the message's internal pointers (in lieu of giving it a proper
|
||||
// copy constructor; see GH#10076) and then tell the wait completion routine (which
|
||||
// is going to be a COOKED_READ, RAW_READ, DirectRead or WriteData) about the new
|
||||
// buffer location.
|
||||
//
|
||||
// This is a scoped fix that should be replaced (TODO GH#10076) with a final one
|
||||
// after Ask mode.
|
||||
_WaitReplyMessage.UpdateUserBufferPointers();
|
||||
|
||||
if (pWaitReplyMessage->State.InputBuffer)
|
||||
{
|
||||
_pWaiter->MigrateUserBuffersOnTransitionToBackgroundWait(pWaitReplyMessage->State.InputBuffer, _WaitReplyMessage.State.InputBuffer);
|
||||
}
|
||||
|
||||
if (pWaitReplyMessage->State.OutputBuffer)
|
||||
{
|
||||
_pWaiter->MigrateUserBuffersOnTransitionToBackgroundWait(pWaitReplyMessage->State.OutputBuffer, _WaitReplyMessage.State.OutputBuffer);
|
||||
}
|
||||
|
||||
// We will write the original message back (with updated out parameters/payload) when the request is finally serviced.
|
||||
if (pWaitReplyMessage->Complete.Write.Data != nullptr)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue