diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index eef0f5056fa..04a0d86cc83 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -31,6 +31,8 @@ IInheritable IMap IObject IStorage +ITaskbar +llabs LCID llabs lround @@ -39,6 +41,7 @@ NCHITTEST NCLBUTTONDBLCLK NCRBUTTONDBLCLK NOAGGREGATION +NOPROGRESS NOREDIRECTIONBITMAP oaidl ocidl @@ -58,6 +61,7 @@ sregex STDCPP strchr syscall +TBPF THEMECHANGED tmp tx diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index fedcbe05944..6e2df0982a9 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -633,6 +633,22 @@ namespace winrt::TerminalApp::implementation return _root->CalcSnappedDimension(widthOrHeight, dimension); } + // Method Description: + // - Gets the user's setting for whether the control that sent an error state + // should be automatically focused + // Return Value: + // - The value of the "autoFocusErrorPane" setting + bool AppLogic::GetAutoFocusErrorPane() + { + if (!_loadedInitialSettings) + { + // Load settings if we haven't already + LoadSettings(); + } + + return _settings.GlobalSettings().AutoFocusErrorPane(); + } + // Method Description: // - Attempt to load the settings. If we fail for any reason, returns an error. // Return Value: @@ -1001,6 +1017,32 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Gets the taskbar state value from the last active control + // Return Value: + // - The taskbar state of the last active control + size_t AppLogic::GetLastActiveControlTaskbarState() + { + if (_root) + { + return _root->GetLastActiveControlTaskbarState(); + } + return {}; + } + + // Method Description: + // - Gets the taskbar progress value from the last active control + // Return Value: + // - The taskbar progress of the last active control + size_t AppLogic::GetLastActiveControlTaskbarProgress() + { + if (_root) + { + return _root->GetLastActiveControlTaskbarProgress(); + } + return {}; + } + // Method Description: // - Sets the initial commandline to process on startup, and attempts to // parse it. Commands will be parsed into a list of ShortcutActions that diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 34875582685..58740c74541 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -41,6 +41,7 @@ namespace winrt::TerminalApp::implementation bool GetShowTabsInTitlebar(); bool GetInitialAlwaysOnTop(); float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; + bool GetAutoFocusErrorPane(); Windows::UI::Xaml::UIElement GetRoot() noexcept; @@ -50,6 +51,9 @@ namespace winrt::TerminalApp::implementation void WindowCloseButtonClicked(); + size_t GetLastActiveControlTaskbarState(); + size_t GetLastActiveControlTaskbarProgress(); + winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); // -------------------------------- WinRT Events --------------------------------- @@ -108,6 +112,7 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged); FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged); FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged); + FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); }; } diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 7056f71b540..041e9b2793a 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -51,6 +51,10 @@ namespace TerminalApp Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension); void TitlebarClicked(); void WindowCloseButtonClicked(); + Boolean GetAutoFocusErrorPane(); + + UInt64 GetLastActiveControlTaskbarState(); + UInt64 GetLastActiveControlTaskbarProgress(); // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. @@ -63,5 +67,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler FocusModeChanged; event Windows.Foundation.TypedEventHandler FullscreenChanged; event Windows.Foundation.TypedEventHandler AlwaysOnTopChanged; + event Windows.Foundation.TypedEventHandler SetTaskbarProgress; } } diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 8641793606d..bf930d412fa 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -45,6 +45,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + _controlWantsFocusToken = _control.ControlWantsFocus({ this, &Pane::_ControlWantsFocusHandler }); // On the first Pane's creation, lookup resources we'll use to theme the // Pane, including the brushed to use for the focused/unfocused border @@ -388,6 +389,18 @@ void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable cons _GotFocusHandlers(shared_from_this()); } +// Event Description: +// - Called when our control requests focus. We'll use this to trigger our PaneWantsFocus callback, +// the tab that's hosting us should have registered a callback which will mark this pane as +// active and tell TerminalPage to focus that tab +// Arguments: +// - +void Pane::_ControlWantsFocusHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, + winrt::Windows::Foundation::IInspectable const& /*e*/) +{ + _PaneWantsFocusHandlers(shared_from_this()); +} + // Method Description: // - Fire our Closed event to tell our parent that we should be removed. // Arguments: @@ -655,6 +668,7 @@ void Pane::_CloseChild(const bool closeFirst) // Add our new event handler before revoking the old one. _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + _controlWantsFocusToken = _control.ControlWantsFocus({ this, &Pane::_ControlWantsFocusHandler }); // Revoke the old event handlers. Remove both the handlers for the panes // themselves closing, and remove their handlers for their controls @@ -666,6 +680,8 @@ void Pane::_CloseChild(const bool closeFirst) remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); closedChild->_control.WarningBell(closedChild->_warningBellToken); remainingChild->_control.WarningBell(remainingChild->_warningBellToken); + closedChild->_control.ControlWantsFocus(closedChild->_controlWantsFocusToken); + remainingChild->_control.ControlWantsFocus(remainingChild->_controlWantsFocusToken); // If either of our children was focused, we want to take that focus from // them. @@ -757,6 +773,7 @@ void Pane::_CloseChild(const bool closeFirst) oldSecond->Closed(oldSecondToken); closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); closedChild->_control.WarningBell(closedChild->_warningBellToken); + closedChild->_control.ControlWantsFocus(closedChild->_controlWantsFocusToken); // Reset our UI: _root.Children().Clear(); @@ -1459,6 +1476,8 @@ std::pair, std::shared_ptr> Pane::_Split(SplitState _connectionStateChangedToken.value = 0; _control.WarningBell(_warningBellToken); _warningBellToken.value = 0; + _control.ControlWantsFocus(_controlWantsFocusToken); + _controlWantsFocusToken.value = 0; // Remove our old GotFocus handler from the control. We don't what the // control telling us that it's now focused, we want it telling its new @@ -2069,3 +2088,4 @@ std::optional Pane::PreCalculateAutoSplit(const std::shared_ptr>); +DEFINE_EVENT(Pane, PaneWantsFocus, _PaneWantsFocusHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 727e3e3f568..e2c58ed99e5 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -77,6 +77,7 @@ class Pane : public std::enable_shared_from_this WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); + DECLARE_EVENT(PaneWantsFocus, _PaneWantsFocusHandlers, winrt::delegate>); private: struct SnapSizeResult; @@ -100,6 +101,7 @@ class Pane : public std::enable_shared_from_this winrt::event_token _firstClosedToken{ 0 }; winrt::event_token _secondClosedToken{ 0 }; winrt::event_token _warningBellToken{ 0 }; + winrt::event_token _controlWantsFocusToken{ 0 }; winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; @@ -135,6 +137,8 @@ class Pane : public std::enable_shared_from_this winrt::Windows::Foundation::IInspectable const& e); void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void _ControlWantsFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::Foundation::IInspectable const& e); std::pair _CalcChildrenSizes(const float fullSize) const; SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index d61a9a9ef81..43c6dcc3a18 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -701,6 +701,20 @@ namespace winrt::TerminalApp::implementation } }); + // When the tab wants focus, we check if its not already focused and focus it + // if so + newTabImpl->TabWantsFocus([weakTab, weakThis{ get_weak() }]() { + auto page{ weakThis.get() }; + auto tab{ weakTab.get() }; + + if (page && tab && tab->FocusState() == Windows::UI::Xaml::FocusState::Unfocused) + { + page->_tabView.SelectedItem(tab->TabViewItem()); + const auto idx = tab->TabViewIndex(); + page->_UpdatedSelectedTab(idx); + } + }); + auto tabViewItem = newTabImpl->TabViewItem(); _tabView.TabItems().Append(tabViewItem); _ReapplyCompactTabSize(); @@ -1118,6 +1132,9 @@ namespace winrt::TerminalApp::implementation term.OpenHyperlink({ this, &TerminalPage::_OpenHyperlinkHandler }); + // Add an event handler for when the terminal wants to set a progress indicator on the taskbar + term.SetTaskbarProgress({ this, &TerminalPage::_SetTaskbarProgressHandler }); + // Bind Tab events to the TermControl and the Tab's Pane hostingTab.Initialize(term); @@ -1882,6 +1899,16 @@ namespace winrt::TerminalApp::implementation return control.CopySelectionToClipboard(singleLine, formats); } + // Method Description: + // - Send an event (which will be caught by AppHost) to set the progress indicator on the taskbar + // Arguments: + // - sender (not used) + // - eventArgs: the arguments specifying how to set the progress indicator + void TerminalPage::_SetTaskbarProgressHandler(const IInspectable sender, const IInspectable /*eventArgs*/) + { + _setTaskbarProgressHandlers(sender, nullptr); + } + // Method Description: // - Paste text from the Windows Clipboard to the focused terminal void TerminalPage::_PasteText() @@ -2263,6 +2290,32 @@ namespace winrt::TerminalApp::implementation _dialogPresenter = dialogPresenter; } + // Method Description: + // - Gets the taskbar state value from the last active control + // Return Value: + // - The taskbar state of the last active control + size_t TerminalPage::GetLastActiveControlTaskbarState() + { + if (auto control{ _GetActiveControl() }) + { + return control.GetTaskbarState(); + } + return {}; + } + + // Method Description: + // - Gets the taskbar progress value from the last active control + // Return Value: + // - The taskbar progress of the last active control + size_t TerminalPage::GetLastActiveControlTaskbarProgress() + { + if (auto control{ _GetActiveControl() }) + { + return control.GetTaskbarProgress(); + } + return {}; + } + // Method Description: // - This is the method that App will call when the titlebar // has been clicked. It dismisses any open flyouts. @@ -2712,4 +2765,5 @@ namespace winrt::TerminalApp::implementation DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, FocusModeChanged, _focusModeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, FullscreenChanged, _fullscreenChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, AlwaysOnTopChanged, _alwaysOnTopChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, SetTaskbarProgress, _setTaskbarProgressHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 97d87d4f78c..a7d46fe7849 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -71,6 +71,9 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::IDialogPresenter DialogPresenter() const; void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter); + size_t GetLastActiveControlTaskbarState(); + size_t GetLastActiveControlTaskbarProgress(); + // -------------------------------- WinRT Events --------------------------------- DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs); @@ -78,6 +81,7 @@ namespace winrt::TerminalApp::implementation DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(FocusModeChanged, _focusModeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(FullscreenChanged, _fullscreenChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(AlwaysOnTopChanged, _alwaysOnTopChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTaskbarProgress, _setTaskbarProgressHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs); private: @@ -191,6 +195,8 @@ namespace winrt::TerminalApp::implementation void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri); bool _CopyText(const bool singleLine, const Windows::Foundation::IReference& formats); + void _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs); + void _PasteText(); fire_and_forget _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 7fb5cb29ac1..d5ae6410ea3 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -27,6 +27,9 @@ namespace TerminalApp // and because of GH#5224. IDialogPresenter DialogPresenter; + UInt64 GetLastActiveControlTaskbarState(); + UInt64 GetLastActiveControlTaskbarProgress(); + event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; event Windows.Foundation.TypedEventHandler SetTitleBarContent; @@ -34,5 +37,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler FullscreenChanged; event Windows.Foundation.TypedEventHandler AlwaysOnTopChanged; event Windows.Foundation.TypedEventHandler Initialized; + event Windows.Foundation.TypedEventHandler SetTaskbarProgress; } } diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 567d48a7ba1..f7b2f87c435 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -105,6 +105,7 @@ namespace winrt::TerminalApp::implementation if (lastFocusedControl) { lastFocusedControl.Focus(_focusState); + lastFocusedControl.TaskbarProgressChanged(); } } } @@ -482,6 +483,22 @@ namespace winrt::TerminalApp::implementation } } }); + + // Add a PaneWantsFocus event handler to the pane. If the pane wants focus, + // we mark it as the active one in this tab's pane tree and inform TerminalPage + // to focus this tab. + pane->PaneWantsFocus([weakThis](std::shared_ptr sender) { + // Do nothing if the Tab's lifetime is expired + auto tab{ weakThis.get() }; + + if (tab) + { + tab->_UpdateActivePane(sender); + tab->_RecalculateAndApplyTabColor(); + tab->_TabWantsFocusHandlers(); + } + + }); } // Method Description: @@ -1019,4 +1036,5 @@ namespace winrt::TerminalApp::implementation DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate); DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>); + DEFINE_EVENT(TerminalTab, TabWantsFocus, _TabWantsFocusHandlers, winrt::delegate<>); } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 7144a961fa4..3fca75bd620 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -69,6 +69,7 @@ namespace winrt::TerminalApp::implementation DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate); DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>); + DECLARE_EVENT(TabWantsFocus, _TabWantsFocusHandlers, winrt::delegate<>); private: std::shared_ptr _rootPane{ nullptr }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index ffc6aeb0f69..b316601e9f5 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -107,6 +107,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation auto pfnCopyToClipboard = std::bind(&TermControl::_CopyToClipboard, this, std::placeholders::_1); _terminal->SetCopyToClipboardCallback(pfnCopyToClipboard); + _terminal->TaskbarProgressChangedCallback([&]() { TermControl::TaskbarProgressChanged(); }); + // This event is explicitly revoked in the destructor: does not need weak_ref auto onReceiveOutputFn = [this](const hstring str) { _terminal->Write(str); @@ -3064,6 +3066,42 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return coreColor.has_value() ? Windows::Foundation::IReference(coreColor.value()) : nullptr; } + // Method Description: + // - Sends an event (which will be caught by TerminalPage and forwarded to AppHost after) + // to set the progress indicator on the taskbar + winrt::fire_and_forget TermControl::TaskbarProgressChanged() + { + co_await resume_foreground(Dispatcher(), CoreDispatcherPriority::High); + _setTaskbarProgressHandlers(*this, nullptr); + } + + // Method Description: + // - Gets the internal taskbar state value + // Return Value: + // - The taskbar state of this control + const size_t TermControl::GetTaskbarState() const noexcept + { + return _terminal->GetTaskbarState(); + } + + // Method Description: + // - Gets the internal taskbar progress value + // Return Value: + // - The taskbar progress of this control + const size_t TermControl::GetTaskbarProgress() const noexcept + { + return _terminal->GetTaskbarProgress(); + } + + // Method Description: + // - Focuses this control if its not already focused + // - Bubbles an event up to the pane, which bubbles an event up to the tab + // which bubbles an event up to the page + void TermControl::TryGettingFocus() noexcept + { + _ControlWantsFocusHandlers(*this, nullptr); + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. @@ -3074,5 +3112,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, OpenHyperlink, _openHyperlinkHandlers, TerminalControl::TermControl, TerminalControl::OpenHyperlinkEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, SetTaskbarProgress, _setTaskbarProgressHandlers, TerminalControl::TermControl, IInspectable); // clang-format on } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index cc7601f728d..c00f1cf36e9 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -141,6 +141,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation Windows::Foundation::IReference TabColor() noexcept; + winrt::fire_and_forget TaskbarProgressChanged(); + const size_t GetTaskbarState() const noexcept; + const size_t GetTaskbarProgress() const noexcept; + void TryGettingFocus() noexcept; + // clang-format off // -------------------------------- WinRT Events --------------------------------- DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); @@ -150,11 +155,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(OpenHyperlink, _openHyperlinkHandlers, TerminalControl::TermControl, TerminalControl::OpenHyperlinkEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTaskbarProgress, _setTaskbarProgressHandlers, TerminalControl::TermControl, IInspectable); TYPED_EVENT(WarningBell, IInspectable, IInspectable); TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable); TYPED_EVENT(Initialized, TerminalControl::TermControl, Windows::UI::Xaml::RoutedEventArgs); TYPED_EVENT(TabColorChanged, IInspectable, IInspectable); + TYPED_EVENT(ControlWantsFocus, IInspectable, IInspectable); // clang-format on private: diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index d8bc580e0e9..9f907407226 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -60,7 +60,9 @@ namespace Microsoft.Terminal.TerminalControl event Windows.Foundation.TypedEventHandler CopyToClipboard; event Windows.Foundation.TypedEventHandler PasteFromClipboard; event Windows.Foundation.TypedEventHandler OpenHyperlink; + event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler WarningBell; + event Windows.Foundation.TypedEventHandler ControlWantsFocus; event Windows.Foundation.TypedEventHandler Initialized; // This is an event handler forwarder for the underlying connection. @@ -90,6 +92,11 @@ namespace Microsoft.Terminal.TerminalControl void SendInput(String input); void ToggleRetroEffect(); + void TaskbarProgressChanged(); + UInt64 GetTaskbarState(); + UInt64 GetTaskbarProgress(); + void TryGettingFocus(); + Windows.Foundation.IReference TabColor { get; }; event Windows.Foundation.TypedEventHandler TabColorChanged; } diff --git a/src/cascadia/TerminalCore/ITerminalApi.hpp b/src/cascadia/TerminalCore/ITerminalApi.hpp index fda9cef77cc..f9f0f06868d 100644 --- a/src/cascadia/TerminalCore/ITerminalApi.hpp +++ b/src/cascadia/TerminalCore/ITerminalApi.hpp @@ -63,6 +63,8 @@ namespace Microsoft::Terminal::Core virtual bool AddHyperlink(std::wstring_view uri, std::wstring_view params) noexcept = 0; virtual bool EndHyperlink() noexcept = 0; + virtual bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept = 0; + protected: ITerminalApi() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 10dc06c19f0..05e4da5fdd6 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -50,7 +50,9 @@ Terminal::Terminal() : _snapOnInput{ true }, _altGrAliasing{ true }, _blockSelection{ false }, - _selection{ std::nullopt } + _selection{ std::nullopt }, + _taskbarState{ 0 }, + _taskbarProgress{ 0 } { auto dispatch = std::make_unique(*this); auto engine = std::make_unique(std::move(dispatch)); @@ -1102,6 +1104,16 @@ void Terminal::SetBackgroundCallback(std::function pfn) no _pfnBackgroundColorChanged.swap(pfn); } +// Method Description: +// - Allows settings a callback for settings the taskbar progress indicator +// Arguments: +// - pfn: a function callback that takes 2 size_t parameters, one indicating the progress state +// and the other indicating the progress value +void Microsoft::Terminal::Core::Terminal::TaskbarProgressChangedCallback(std::function pfn) noexcept +{ + _pfnTaskbarProgressChanged.swap(pfn); +} + void Terminal::_InitializeColorTable() try { @@ -1168,3 +1180,21 @@ BlinkingState& Terminal::GetBlinkingState() const noexcept { return _blinkingState; } + +// Method Description: +// - Gets the internal taskbar state value +// Return Value: +// - The taskbar state +const size_t Microsoft::Terminal::Core::Terminal::GetTaskbarState() const noexcept +{ + return _taskbarState; +} + +// Method Description: +// - Gets the internal taskbar progress value +// Return Value: +// - The taskbar progress +const size_t Microsoft::Terminal::Core::Terminal::GetTaskbarProgress() const noexcept +{ + return _taskbarProgress; +} diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index c9b30378dfb..c691fc99f06 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -115,6 +115,8 @@ class Microsoft::Terminal::Core::Terminal final : bool AddHyperlink(std::wstring_view uri, std::wstring_view params) noexcept override; bool EndHyperlink() noexcept override; + + bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept override; #pragma endregion #pragma region ITerminalInput @@ -185,6 +187,7 @@ class Microsoft::Terminal::Core::Terminal final : void SetScrollPositionChangedCallback(std::function pfn) noexcept; void SetCursorPositionChangedCallback(std::function pfn) noexcept; void SetBackgroundCallback(std::function pfn) noexcept; + void TaskbarProgressChangedCallback(std::function pfn) noexcept; void SetCursorOn(const bool isOn); bool IsCursorBlinkingAllowed() const noexcept; @@ -196,6 +199,9 @@ class Microsoft::Terminal::Core::Terminal final : Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept; + const size_t GetTaskbarState() const noexcept; + const size_t GetTaskbarProgress() const noexcept; + #pragma region TextSelection // These methods are defined in TerminalSelection.cpp enum class SelectionExpansionMode @@ -221,6 +227,7 @@ class Microsoft::Terminal::Core::Terminal final : std::function _pfnBackgroundColorChanged; std::function _pfnCursorPositionChanged; std::function)> _pfnTabColorChanged; + std::function _pfnTaskbarProgressChanged; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput; @@ -240,6 +247,9 @@ class Microsoft::Terminal::Core::Terminal final : bool _altGrAliasing; bool _suppressApplicationTitle; + size_t _taskbarState; + size_t _taskbarProgress; + size_t _hyperlinkPatternId; #pragma region Text Selection diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 8ed4094184f..cccd9856a8c 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -600,3 +600,21 @@ bool Terminal::EndHyperlink() noexcept _buffer->SetCurrentAttributes(attr); return true; } + +// Method Description: +// - Updates the taskbar progress indicator +// Arguments: +// - state: indicates the progress state +// - progress: indicates the progress value +// Return Value: +// - true +bool Terminal::SetTaskbarProgress(const size_t state, const size_t progress) noexcept +{ + _taskbarState = state; + _taskbarProgress = progress; + if (_pfnTaskbarProgressChanged) + { + _pfnTaskbarProgressChanged(); + } + return true; +} diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index a035febf152..b6c0cab49e7 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -399,6 +399,29 @@ bool TerminalDispatch::EndHyperlink() noexcept return _terminalApi.EndHyperlink(); } +// Method Description: +// - Updates the taskbar progress indicator +// Arguments: +// - state: indicates the progress state +// - progress: indicates the progress value +// Return Value: +// - true +bool TerminalDispatch::SetTaskbarProgress(const size_t state, const size_t progress) noexcept +{ + auto clampedProgress = progress; + if (state > TaskbarMaxState) + { + // state is out of bounds, return false + return false; + } + if (progress > TaskbarMaxProgress) + { + // progress is greater than the maximum allowed value, clamp it to the max + clampedProgress = TaskbarMaxProgress; + } + return _terminalApi.SetTaskbarProgress(state, clampedProgress); +} + // Routine Description: // - Support routine for routing private mode parameters to be set/reset as flags // Arguments: diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index 5e409161d26..566c88e534a 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -4,6 +4,9 @@ #include "../../terminal/adapter/termDispatch.hpp" #include "ITerminalApi.hpp" +static constexpr size_t TaskbarMaxState{ 4 }; +static constexpr size_t TaskbarMaxProgress{ 100 }; + class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatch { public: @@ -67,6 +70,8 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc bool AddHyperlink(const std::wstring_view uri, const std::wstring_view params) noexcept override; bool EndHyperlink() noexcept override; + bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept override; + private: ::Microsoft::Terminal::Core::ITerminalApi& _terminalApi; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 03bda082c89..3691099cad7 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -39,6 +39,7 @@ static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" }; static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" }; static constexpr std::string_view UseTabSwitcherKey{ "useTabSwitcher" }; static constexpr std::string_view DisableAnimationsKey{ "disableAnimations" }; +static constexpr std::string_view AutoFocusErrorPaneKey{ "autoFocusErrorPane" }; static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" }; @@ -109,6 +110,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_AlwaysOnTop = _AlwaysOnTop; globals->_UseTabSwitcher = _UseTabSwitcher; globals->_DisableAnimations = _DisableAnimations; + globals->_AutoFocusErrorPane = _AutoFocusErrorPane; globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile; globals->_validDefaultProfile = _validDefaultProfile; @@ -290,6 +292,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, DisableAnimationsKey, _DisableAnimations); + JsonUtils::GetValueForKey(json, AutoFocusErrorPaneKey, _AutoFocusErrorPane); + // This is a helper lambda to get the keybindings and commands out of both // and array of objects. We'll use this twice, once on the legacy // `keybindings` key, and again on the newer `bindings` key. diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index e93c6c81f96..bf222bfe625 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -83,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation GETSET_SETTING(bool, AlwaysOnTop, false); GETSET_SETTING(bool, UseTabSwitcher, true); GETSET_SETTING(bool, DisableAnimations, false); + GETSET_SETTING(bool, AutoFocusErrorPane, true); private: guid _defaultProfile; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 430e76b13bb..e4cae3d1ea0 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -128,6 +128,10 @@ namespace Microsoft.Terminal.Settings.Model void ClearDisableAnimations(); Boolean DisableAnimations; + Boolean HasAutoFocusErrorPane(); + void ClearAutoFocusErrorPane(); + Boolean AutoFocusErrorPane; + Windows.Foundation.Collections.IMapView ColorSchemes(); void AddColorScheme(ColorScheme scheme); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 08a58ef60d3..2060e444c2e 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -38,6 +38,8 @@ namespace TerminalCoreUnitTests TEST_METHOD(AddHyperlink); TEST_METHOD(AddHyperlinkCustomId); TEST_METHOD(AddHyperlinkCustomIdDifferentUri); + + TEST_METHOD(SetTaskbarProgress); }; }; @@ -339,3 +341,50 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(oldAttributes.GetHyperlinkId()), L"test.url"); VERIFY_ARE_NOT_EQUAL(oldAttributes.GetHyperlinkId(), tbi.GetCurrentAttributes().GetHyperlinkId()); } + +void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() +{ + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + auto& stateMachine = *(term._stateMachine); + + // Initial values for taskbar state and progress should be 0 + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); + + // Set some values for taskbar state and progress through state machine + stateMachine.ProcessString(L"\x1b]9;4;1;50\x9c"); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(1)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(50)); + + // Reset to 0 + stateMachine.ProcessString(L"\x1b]9;4;0;0\x9c"); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); + + // Set an out of bounds value for state + stateMachine.ProcessString(L"\x1b]9;4;5;50\x9c"); + // Nothing should have changed (dispatch should have returned false) + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); + + // Set an out of bounds value for progress + stateMachine.ProcessString(L"\x1b]9;4;1;999\x9c"); + // Progress should have been clamped to 100 + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(1)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(100)); + + // Don't specify any params + stateMachine.ProcessString(L"\x1b]9;4\x9c"); + // State and progress should both be reset to 0 + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); + + // Specify additional params + stateMachine.ProcessString(L"\x1b]9;4;1;80;123\x9c"); + // Additional params should be ignored, state and progress still set normally + VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(1)); + VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(80)); +} diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 5c258b6c59b..10a10b7fb7c 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -79,6 +79,30 @@ bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, cons return false; } +// Method Description: +// - Event handler to update the taskbar progress indicator +// - Upon receiving the event, we check the sender's taskbar state, if its an error state then we +// flash the taskbar and focus that control. Otherwise, we ask the underlying logic for the taskbar +// state/progress values of the last active control (not necessarily the sender). +// Arguments: +// - sender: the control that sent the event +// - args: not used +void AppHost::SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/) +{ + auto control{ sender.try_as() }; + if (_logic && control) + { + // if the sender sent an error state, check the settings to see if we need to + // focus the sender and flash the taskbar + if (_logic.GetAutoFocusErrorPane() && control.GetTaskbarState() == 2) + { + _window->FlashTaskbar(true); + control.TryGettingFocus(); + } + _window->SetTaskbarProgress(_logic.GetLastActiveControlTaskbarState(), _logic.GetLastActiveControlTaskbarProgress()); + } +} + // Method Description: // - Retrieve any commandline args passed on the commandline, and pass them to // the app logic for processing. @@ -171,6 +195,7 @@ void AppHost::Initialize() _logic.TitleChanged({ this, &AppHost::AppTitleChanged }); _logic.LastTabClosed({ this, &AppHost::LastTabClosed }); + _logic.SetTaskbarProgress({ this, &AppHost::SetTaskbarProgress }); _window->UpdateTitle(_logic.Title()); diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 4bdc45c5d29..23e7c534d94 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -18,6 +18,7 @@ class AppHost void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args); void Initialize(); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); + void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); private: bool _useNonClientArea; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index f7beb5cd584..d159764e515 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -261,6 +261,16 @@ void IslandWindow::Initialize() _rootGrid = winrt::Windows::UI::Xaml::Controls::Grid(); _source.Content(_rootGrid); + + // initialize the taskbar object + HRESULT hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_taskbar)); + if (SUCCEEDED(hr)) + { + if (SUCCEEDED(_taskbar->HrInit())) + { + _taskbarInitialized = true; + } + } } void IslandWindow::OnSize(const UINT width, const UINT height) @@ -509,6 +519,55 @@ void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop) } } +// Method Description: +// - Sets the taskbar progress indicator +// - We follow the ConEmu style for the state and progress values, +// more details at https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC +// Arguments: +// - state: indicates the progress state +// - progress: indicates the progress value +void IslandWindow::SetTaskbarProgress(const size_t state, const size_t progress) +{ + if (_taskbarInitialized) + { + switch (state) + { + case 0: + // removes the taskbar progress indicator + _taskbar->SetProgressState(_window.get(), TBPF_NOPROGRESS); + break; + case 1: + // sets the progress value to value given by the 'progress' parameter + _taskbar->SetProgressState(_window.get(), TBPF_NORMAL); + _taskbar->SetProgressValue(_window.get(), progress, 100); + break; + case 2: + // sets the progress indicator to an error state + _taskbar->SetProgressState(_window.get(), TBPF_ERROR); + _taskbar->SetProgressValue(_window.get(), progress, 100); + break; + case 3: + // sets the progress indicator to an indeterminate state + _taskbar->SetProgressState(_window.get(), TBPF_INDETERMINATE); + break; + case 4: + // sets the progress indicator to a pause state + _taskbar->SetProgressState(_window.get(), TBPF_PAUSED); + _taskbar->SetProgressValue(_window.get(), progress, 100); + break; + default: + break; + } + } +} + +// Method Description: +// - Flashes the icon on the taskbar +void IslandWindow::FlashTaskbar(const bool bInvert) +{ + FlashWindow(_window.get(), bInvert); +} + // From GdiEngine::s_SetWindowLongWHelper void _SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept { diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 8e6a4fcc943..64b3febe92a 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -35,6 +35,9 @@ class IslandWindow : void FullscreenChanged(const bool fullscreen); void SetAlwaysOnTop(const bool alwaysOnTop); + void SetTaskbarProgress(const size_t state, const size_t progress); + void FlashTaskbar(const bool bInvert); + #pragma endregion DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); @@ -74,6 +77,9 @@ class IslandWindow : LONG _getDesiredWindowStyle() const; + wil::com_ptr _taskbar; + bool _taskbarInitialized{ false }; + private: // This minimum width allows for width the tabs fit static constexpr long minimumWidth = 460L; diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index 7d82654a5e6..5b2830575ab 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -29,6 +29,7 @@ Module Name: #include #include #include +#include // Manually include til after we include Windows.Foundation to give it winrt superpowers #define BLOCK_TIL diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 9be6e3146b7..5feccdd851c 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -122,6 +122,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool AddHyperlink(const std::wstring_view uri, const std::wstring_view params) = 0; virtual bool EndHyperlink() = 0; + + virtual bool SetTaskbarProgress(const size_t state, const size_t progress) = 0; }; inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() {} #pragma warning(pop) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 25e896f601f..f9115812dbd 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2383,6 +2383,16 @@ bool AdaptDispatch::EndHyperlink() return _pConApi->PrivateEndHyperlink(); } +// Method Description: +// - Ascribes to the ITermDispatch interface +// - Not actually used in conhost +// Return Value: +// - false (so that the command gets flushed to terminal) +bool AdaptDispatch::SetTaskbarProgress(const size_t /*state*/, const size_t /*progress*/) noexcept +{ + return false; +} + // Routine Description: // - Determines whether we should pass any sequence that manipulates // TerminalInput's input generator through the PTY. It encapsulates diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 6644c3e0228..e0945e6b4a6 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -123,6 +123,8 @@ namespace Microsoft::Console::VirtualTerminal bool AddHyperlink(const std::wstring_view uri, const std::wstring_view params) override; bool EndHyperlink() override; + bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept override; + private: enum class ScrollDirection { diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index cb400814f4c..8cc4086b7e9 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -116,4 +116,6 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool AddHyperlink(const std::wstring_view /*uri*/, const std::wstring_view /*params*/) noexcept override { return false; } bool EndHyperlink() noexcept override { return false; } + + bool SetTaskbarProgress(const size_t /*state*/, const size_t /*progress*/) noexcept override { return false; } }; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 81d2ab48142..72b9c23841a 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -647,6 +647,8 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/, bool queryClipboard = false; std::vector tableIndexes; std::vector colors; + size_t state = 0; + size_t progress = 0; switch (parameter) { @@ -672,6 +674,9 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/, case OscActionCodes::Hyperlink: success = _ParseHyperlink(string, params, uri); break; + case OscActionCodes::SetTaskbarProgress: + success = _GetTaskbarProgress(string, state, progress); + break; default: // If no functions to call, overall dispatch was a failure. success = false; @@ -739,6 +744,9 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/, success = _dispatch->AddHyperlink(uri, params); } break; + case OscActionCodes::SetTaskbarProgress: + success = _dispatch->SetTaskbarProgress(state, progress); + break; default: // If no functions to call, overall dispatch was a failure. success = false; @@ -924,6 +932,66 @@ bool OutputStateMachineEngine::_ParseHyperlink(const std::wstring_view string, return false; } +// Routine Description: +// - OSC 9 ; 4 ; state ; progress ST +// state: the state to set the taskbar progress indicator to +// progress: the progress value to set the taskbar progress indicator with +// - Parses out the 'state' and 'progress' parameters from the OSC string so +// that we can use them to set the taskbar progress indicator +// - This follows the ConEmu style, more details here: +// https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC +// Arguments: +// - string: the string to parse +// - state: where to store the state value once we parse it +// - progress: where to store the progress value once we parse it +// Return Value: +// - true if we successfully parsed the string, false otherwise +bool OutputStateMachineEngine::_GetTaskbarProgress(const std::wstring_view string, + size_t& state, + size_t& progress) const +{ + const auto parts = Utils::SplitString(string, L';'); + + unsigned int subParam = 0; + const auto subParamSuccess = Utils::StringToUint(til::at(parts, 0), subParam); + + if (parts.size() < 1 || !subParamSuccess || subParam != 4) + { + return false; + } + + if (parts.size() == 1) + { + state = 0; + progress = 0; + } + else if (parts.size() == 2) + { + unsigned int localState = 0; + const auto stateSuccess = Utils::StringToUint(til::at(parts, 1), localState); + if (!stateSuccess) + { + return false; + } + state = localState; + progress = 0; + } + else + { + unsigned int localState = 0; + unsigned int localProgress = 0; + const auto stateSuccess = Utils::StringToUint(til::at(parts, 1), localState); + const auto progressSuccess = Utils::StringToUint(til::at(parts, 2), localProgress); + if (!stateSuccess || !progressSuccess) + { + return false; + } + state = localState; + progress = localProgress; + } + return true; +} + // Routine Description: // - OSC 10, 11, 12 ; spec ST // spec: The colors are specified by name or RGB specification as per XParseColor diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 2d64f22e063..1f11bc1f851 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -160,6 +160,7 @@ namespace Microsoft::Console::VirtualTerminal SetWindowProperty = 3, // Not implemented SetColor = 4, Hyperlink = 8, + SetTaskbarProgress = 9, SetForegroundColor = 10, SetBackgroundColor = 11, SetCursorColor = 12, @@ -188,6 +189,10 @@ namespace Microsoft::Console::VirtualTerminal std::wstring& params, std::wstring& uri) const; + bool _GetTaskbarProgress(const std::wstring_view string, + size_t& state, + size_t& progress) const; + void _ClearLastChar() noexcept; }; } diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index 63e39a52d77..201a30754b6 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -1456,6 +1456,11 @@ class StatefulDispatch final : public TermDispatch return true; } + bool SetTaskbarProgress(const size_t /*state*/, const size_t /*progress*/) noexcept override + { + return true; + } + size_t _cursorDistance; size_t _line; size_t _column;