// 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 "ControlSettings.h" #include "../../audio/midi/MidiAudio.hpp" #include "../../buffer/out/search.h" #include "../../cascadia/TerminalCore/Terminal.hpp" #include "../../renderer/inc/FontInfoDesired.hpp" 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); ~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; winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept; void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme); ::Microsoft::Console::Render::Renderer* GetRenderer() const noexcept; uint64_t SwapChainHandle() const; void AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings); 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 Windows::Foundation::IReference& 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 PersistToPath(const wchar_t* path) 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(); int ViewHeight() 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(); winrt::hstring CurrentWorkingDirectory() const; #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 void BlinkAttributeTick(); void BlinkCursor(); bool CursorOn() const; void CursorOn(const bool isCursorOn); bool IsVtMouseModeEnabled() const; bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const; Core::Point CursorPosition() const; bool CopyOnSelect() const; Control::SelectionData SelectionInfo() const; void SetSelectionAnchor(const til::point position); void SetEndSelectionPoint(const til::point position); SearchResults Search(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 AnchorContextMenu(til::point viewportRelativeCharacterPosition); bool ShouldShowSelectCommand(); bool ShouldShowSelectOutput(); void PreviewInput(std::wstring_view input); 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<> 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<> 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 _refreshSizeUnderLock(); void _updateSelectionUI(); bool _shouldTryUpdateSelection(const WORD vkey); void _handleControlC(); void _sendInputToConnection(std::wstring_view wstr); #pragma region TerminalCoreCallbacks void _terminalCopyToClipboard(wil::zwstring_view wstr); 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 _terminalWindowSizeChanged(int32_t width, int32_t height); safe_void_coroutine _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(); #pragma endregion void _raiseReadOnlyWarning(); void _updateAntiAliasingMode(); void _connectionOutputHandler(const hstring& hstr); void _connectionStateChangedHandler(const TerminalConnection::ITerminalConnection&, const Windows::Foundation::IInspectable&); void _updateHoveredCell(const std::optional terminalPosition); 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; // Rendering stuff. winrt::handle _lastSwapChainHandle{ nullptr }; uint64_t _owningHwnd{ 0 }; float _panelWidth{ 0 }; float _panelHeight{ 0 }; float _compositionScale{ 0 }; // Audio stuff. MidiAudio _midiAudio; winrt::Windows::System::DispatcherQueueTimer _midiAudioSkipTimer{ nullptr }; // Other stuff. winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; winrt::com_ptr _settings{ 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 }; // ---------------------------------------------------------------------------------------- // 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); }