// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // // Module Name: // - ControlCore.h // // Abstract: // - This encapsulates a `Terminal` instance, a `AtlasEngine` and `Renderer`, and // an `ITerminalConnection`. This is intended to be everything that someone // might need to stand up a terminal instance in a control, but without any // regard for how the UX works. // // Author: // - Mike Griese (zadjii-msft) 01-Apr-2021 #pragma once #include "ControlCore.g.h" #include "SelectionColor.g.h" #include "CommandHistoryContext.g.h" #include "../../audio/midi/MidiAudio.hpp" #include "../../buffer/out/search.h" #include "../../cascadia/TerminalCore/Terminal.hpp" #include "../../renderer/inc/FontInfoDesired.hpp" #include namespace Microsoft::Console::Render::Atlas { class AtlasEngine; } namespace Microsoft::Console::Render { class UiaEngine; } namespace ControlUnitTests { class ControlCoreTests; class ControlInteractivityTests; }; #define RUNTIME_SETTING(type, name, setting) \ private: \ std::optional _runtime##name{ std::nullopt }; \ void name(const type newValue) \ { \ _runtime##name = newValue; \ } \ \ public: \ type name() const \ { \ return til::coalesce_value(_runtime##name, setting); \ } namespace winrt::Microsoft::Terminal::Control::implementation { struct SelectionColor : SelectionColorT { TextColor AsTextColor() const noexcept; til::property Color; til::property IsIndex16; }; struct CommandHistoryContext : CommandHistoryContextT { til::property> History; til::property CurrentCommandline; til::property> QuickFixes; CommandHistoryContext(std::vector&& history) : QuickFixes(winrt::single_threaded_vector()) { History(winrt::single_threaded_vector(std::move(history))); } }; struct ControlCore : ControlCoreT { public: ControlCore(Control::IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection, Windows::System::DispatcherQueue dispatcher = nullptr); ~ControlCore(); bool Initialize(const float actualWidth, const float actualHeight, const float compositionScale); void EnablePainting(); void Detach(); void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance); void ApplyAppearance(const bool focused); void SetHighContrastMode(const bool enabled); Control::IControlSettings Settings(); Control::IControlAppearance FocusedAppearance() const; Control::IControlAppearance UnfocusedAppearance() const; bool HasUnfocusedAppearance() const; void ApplyPreviewColorScheme(const Core::ICoreScheme&); void ResetPreviewColorScheme(); void SetOverrideColorScheme(const Core::ICoreScheme&); ::Microsoft::Console::Render::Renderer* GetRenderer() const noexcept; HANDLE SwapChainHandle() const; void AttachToNewControl(); void SizeChanged(const float width, const float height); void ScaleChanged(const float scale); void SizeOrScaleChanged(const float width, const float height, const float scale); void AdjustFontSize(float fontSizeDelta); void ResetFontSize(); FontInfo GetFont() const; winrt::Windows::Foundation::Size FontSizeInDips() const; winrt::Windows::Foundation::Size FontSize() const noexcept; uint16_t FontWeight() const noexcept; til::color ForegroundColor() const; til::color BackgroundColor() const; void SendInput(std::wstring_view wstr); void PasteText(const winrt::hstring& hstr); bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const CopyFormat formats); void SelectAll(); void ClearSelection(); bool ToggleBlockSelection(); void ToggleMarkMode(); Control::SelectionInteractionMode SelectionMode() const; bool SwitchSelectionEndpoint(); bool ExpandSelectionToWord(); bool TryMarkModeKeybinding(const WORD vkey, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers); void GotFocus(); void LostFocus(); void ToggleShaderEffects(); void AdjustOpacity(const float adjustment); void ResumeRendering(); void SetHoveredCell(Core::Point terminalPosition); void ClearHoveredCell(); winrt::hstring GetHyperlink(const Core::Point position) const; winrt::hstring HoveredUriText() const; Windows::Foundation::IReference HoveredCell() const; ::Microsoft::Console::Render::IRenderData* GetRenderData() const; void ColorSelection(const Control::SelectionColor& fg, const Control::SelectionColor& bg, Core::MatchMode matchMode); void Close(); void PersistTo(HANDLE handle) const; void RestoreFromPath(const wchar_t* path) const; void ClearQuickFix(); void OpenCWD(); #pragma region ICoreState const size_t TaskbarState() const noexcept; const size_t TaskbarProgress() const noexcept; hstring Title(); Windows::Foundation::IReference TabColor() noexcept; hstring WorkingDirectory() const; TerminalConnection::ConnectionState ConnectionState() const; int ScrollOffset(); Core::Size ViewportSize() const; int BufferHeight() const; bool HasSelection() const; bool HasMultiLineSelection() const; winrt::hstring SelectedText(bool trimTrailingWhitespace) const; bool BracketedPasteEnabled() const noexcept; Windows::Foundation::Collections::IVector ScrollMarks() const; void AddMark(const Control::ScrollMark& mark); void ClearMark(); void ClearAllMarks(); void ScrollToMark(const Control::ScrollToMarkDirection& direction); void SelectCommand(const bool goUp); void SelectOutput(const bool goUp); void ContextMenuSelectCommand(); void ContextMenuSelectOutput(); #pragma endregion #pragma region ITerminalInput bool TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown); bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers); bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ::Microsoft::Terminal::Core::ControlKeyStates states, const short wheelDelta, const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state); void UserScrollViewport(const int viewTop); void ClearBuffer(Control::ClearBufferType clearType); #pragma endregion bool IsVtMouseModeEnabled() const; bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const; Core::Point CursorPosition() const; bool ForceCursorVisible() const noexcept; void ForceCursorVisible(bool force); bool CopyOnSelect() const; Control::SelectionData SelectionInfo() const; void SetSelectionAnchor(const til::point position); void SetEndSelectionPoint(const til::point position); SearchResults Search(const SearchRequest& request); const std::vector& SearchResultRows() const noexcept; void ClearSearch(); void LeftClickOnTerminal(const til::point terminalPosition, const int numberOfClicks, const bool altEnabled, const bool shiftEnabled, const bool isOnOriginalPosition, bool& selectionNeedsToBeCopied); void AttachUiaEngine(::Microsoft::Console::Render::UiaEngine* const pEngine); void DetachUiaEngine(::Microsoft::Console::Render::UiaEngine* const pEngine); bool IsInReadOnlyMode() const; void ToggleReadOnlyMode(); void SetReadOnlyMode(const bool readOnlyState); hstring ReadEntireBuffer() const; Control::CommandHistoryContext CommandHistory() const; bool QuickFixesAvailable() const noexcept; void UpdateQuickFixes(const Windows::Foundation::Collections::IVector& quickFixes); void AdjustOpacity(const float opacity, const bool relative); void WindowVisibilityChanged(const bool showOrHide); uint64_t OwningHwnd(); void OwningHwnd(uint64_t owner); TerminalConnection::ITerminalConnection Connection(); void Connection(const TerminalConnection::ITerminalConnection& connection); void HardResetWithoutErase(); void AnchorContextMenu(til::point viewportRelativeCharacterPosition); bool ShouldShowSelectCommand(); bool ShouldShowSelectOutput(); void PreviewInput(std::wstring_view input); using TimerHandle = ::Microsoft::Console::Render::TimerHandle; TimerHandle RegisterRenderTimer(const char* name, std::function callback); bool IsRenderTimerRunning(TimerHandle h); void StartRepeatingRenderTimer(TimerHandle h, uint64_t micros); void StopRenderTimer(TimerHandle h); RUNTIME_SETTING(float, Opacity, _settings.Opacity()); RUNTIME_SETTING(float, FocusedOpacity, FocusedAppearance().Opacity()); RUNTIME_SETTING(bool, UseAcrylic, _settings.UseAcrylic()); // -------------------------------- WinRT Events --------------------------------- // clang-format off til::typed_event FontSizeChanged; til::typed_event TitleChanged; til::typed_event WriteToClipboard; til::typed_event<> WarningBell; til::typed_event<> TabColorChanged; til::typed_event<> BackgroundColorChanged; til::typed_event ScrollPositionChanged; til::typed_event<> TaskbarProgressChanged; til::typed_event<> ConnectionStateChanged; til::typed_event<> HoveredHyperlinkChanged; til::typed_event RendererEnteredErrorState; til::typed_event<> SwapChainChanged; til::typed_event RendererWarning; til::typed_event RaiseNotice; til::typed_event TransparencyChanged; til::typed_event<> OutputIdle; til::typed_event ShowWindowChanged; til::typed_event UpdateSelectionMarkers; til::typed_event OpenHyperlink; til::typed_event CompletionsChanged; til::typed_event SearchMissingCommand; til::typed_event ShowNotification; til::typed_event<> RefreshQuickFixUI; til::typed_event WindowSizeChanged; til::typed_event<> CloseTerminalRequested; til::typed_event<> RestartTerminalRequested; til::typed_event<> Attached; // clang-format on private: struct SharedState { std::unique_ptr> outputIdle; std::unique_ptr> focusChanged; std::shared_ptr> updateScrollBar; }; void _setupDispatcherAndCallbacks(); void _closeConnection(); bool _setFontSizeUnderLock(float fontSize); void _updateFont(); void _raiseFontSizeChanged(); void _refreshSizeUnderLock(); void _updateSelectionUI(); bool _shouldTryUpdateSelection(const WORD vkey); void _handleControlC(); void _sendInputToConnection(std::wstring_view wstr); #pragma region TerminalCoreCallbacks void _terminalWarningBell(); void _terminalTitleChanged(std::wstring_view wstr); void _terminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize); void _terminalTaskbarProgressChanged(); void _terminalShowWindowChanged(bool showOrHide); void _terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration); void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow); void _terminalShowNotification(std::wstring_view title, std::wstring_view body); void _terminalWindowSizeChanged(int32_t width, int32_t height); void _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); #pragma endregion #pragma region RendererCallbacks void _rendererWarning(const HRESULT hr, wil::zwstring_view parameter); safe_void_coroutine _renderEngineSwapChainChanged(const HANDLE handle); void _rendererBackgroundColorChanged(); void _rendererTabColorChanged(); void _rendererEnteredErrorState(); #pragma endregion void _raiseReadOnlyWarning(); void _updateAntiAliasingMode(); void _connectionOutputHandler(winrt::array_view str); void _connectionStateChangedHandler(const TerminalConnection::ITerminalConnection&, const Windows::Foundation::IInspectable&); void _updateHoveredCell(const std::optional terminalPosition); void _refreshHoveredCell(); void _setOpacity(const float opacity, const bool focused = true); bool _isBackgroundTransparent(); void _focusChanged(bool focused); void _selectSpan(til::point_span s); void _repositionCursorWithMouse(const til::point terminalPosition); void _contextMenuSelectMark( const til::point& pos, bool (*filter)(const ::MarkExtents&), til::point_span (*getSpan)(const ::MarkExtents&)); bool _clickedOnMark(const til::point& pos, bool (*filter)(const ::MarkExtents&)); inline bool _IsClosing() const noexcept { #ifndef NDEBUG if (_dispatcher) { // _closing isn't atomic and may only be accessed from the main thread. // // Though, the unit tests don't actually run in TAEF's main // thread, so we don't care when we're running in tests. assert(_inUnitTests || _dispatcher.HasThreadAccess()); } #endif return _closing; } // Caches responses generated by our VT parser (= improved batching). std::wstring _pendingResponses; // Font stuff. FontInfoDesired _desiredFont; FontInfo _actualFont; bool _builtinGlyphs = true; bool _colorGlyphs = true; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; float _accumulatedFontSizeDelta = 0.f; // Preserved across reloads to prevent user zoom from being overwritten. // Rendering stuff. winrt::handle _lastSwapChainHandle{ nullptr }; uint64_t _owningHwnd{ 0 }; float _panelWidth{ 0 }; float _panelHeight{ 0 }; float _compositionScale{ 0 }; uint8_t _renderFailures{ 0 }; bool _forceCursorVisible = false; // Audio stuff. MidiAudio _midiAudio; wil::unique_threadpool_timer _midiAudioSkipTimer{}; // Other stuff. winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; IControlSettings _settings{ nullptr }; bool _hasUnfocusedAppearance{ false }; IControlAppearance _unfocusedAppearance{ nullptr }; Core::ICoreScheme _focusedColorSchemeOverride{ nullptr }; til::point _contextMenuBufferPosition{ 0, 0 }; Windows::Foundation::Collections::IVector _cachedQuickFixes{ nullptr }; ::Search _searcher; std::optional::interval> _lastHoveredInterval; std::optional _leadingSurrogate; std::optional _lastHoveredCell; uint16_t _lastHoveredId{ 0 }; std::atomic _initializedTerminal{ false }; bool _isReadOnly{ false }; bool _closing{ false }; struct StashedColorScheme { std::array scheme; size_t foregroundAlias; size_t backgroundAlias; }; std::unique_ptr _stashedColorScheme; // ---------------------------------------------------------------------------------------- // These are ordered last to ensure they're destroyed first. // This ensures that their respective contents stops taking dependency on the above. // I recommend reading the following paragraphs in reverse order. // ---------------------------------------------------------------------------------------- // ↑ This one is tricky - all of these are raw pointers: // 1. _terminal depends on _renderer (for invalidations) // 2. _renderer depends on _terminal (for IRenderData) // = circular dependency = architectural flaw (lifetime issues) = TODO // 3. _renderer depends on _renderEngine (AtlasEngine) // To solve the knot, we manually stop the renderer in the destructor, // which breaks 2. We can proceed then proceed to break 1. and then 3. std::unique_ptr<::Microsoft::Console::Render::Atlas::AtlasEngine> _renderEngine{ nullptr }; // 3. std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr }; // 3. std::shared_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr }; // 1. // ↑ MOST IMPORTANTLY: `_outputIdle` takes dependency on the raw `this` pointer (necessarily). // Destroying SharedState here will block until all pending `debounced_func_trailing` calls are completed. til::shared_mutex _shared; // ↑ Prevent any more unnecessary `_outputIdle` calls. // Technically none of these members are destroyed here. Instead, the destructor will call Close() // which calls _closeConnection() which in turn manually & safely destroys them in the correct order. TerminalConnection::ITerminalConnection::TerminalOutput_revoker _connectionOutputEventRevoker; TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker; TerminalConnection::ITerminalConnection _connection{ nullptr }; friend class ControlUnitTests::ControlCoreTests; friend class ControlUnitTests::ControlInteractivityTests; bool _inUnitTests{ false }; }; } namespace winrt::Microsoft::Terminal::Control::factory_implementation { BASIC_FACTORY(ControlCore); BASIC_FACTORY(SelectionColor); }