From c49bc1a789b096bc1ae1aaa9178da7876d638793 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Mar 2026 15:29:11 -0700 Subject: [PATCH 01/24] Add toast notification infrastructure --- .../TerminalApp/AppCommandlineArgs.cpp | 9 ++ src/cascadia/TerminalApp/BasicPaneEvents.h | 1 + .../TerminalApp/DesktopNotification.cpp | 112 ++++++++++++++++++ .../TerminalApp/DesktopNotification.h | 38 ++++++ src/cascadia/TerminalApp/IPaneContent.idl | 7 ++ .../Resources/en-US/Resources.resw | 12 ++ src/cascadia/TerminalApp/Tab.cpp | 20 ++++ src/cascadia/TerminalApp/Tab.h | 2 + src/cascadia/TerminalApp/TabManagement.cpp | 71 +++++++++++ .../TerminalApp/TerminalAppLib.vcxproj | 2 + src/cascadia/TerminalApp/TerminalPage.h | 2 + .../TerminalApp/TerminalPaneContent.h | 11 ++ src/cascadia/TerminalApp/pch.h | 3 + 13 files changed, 290 insertions(+) create mode 100644 src/cascadia/TerminalApp/DesktopNotification.cpp create mode 100644 src/cascadia/TerminalApp/DesktopNotification.h diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index c41eea5431..a7ae09a01f 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -1068,6 +1068,15 @@ int AppCommandlineArgs::ParseArgs(winrt::array_view args) return 0; } + // When a toast notification is clicked, Windows may launch a new instance + // with "__fromToast" as the argument. This is a no-op sentinel — the + // in-process Activated handler on the toast already handled activation. + // See DesktopNotification.cpp for more details. + if (args.size() == 2 && args[1] == L"__fromToast") + { + return 0; + } + auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args); for (auto& cmdBlob : commands) diff --git a/src/cascadia/TerminalApp/BasicPaneEvents.h b/src/cascadia/TerminalApp/BasicPaneEvents.h index e82ff0f49b..bd9bfb3916 100644 --- a/src/cascadia/TerminalApp/BasicPaneEvents.h +++ b/src/cascadia/TerminalApp/BasicPaneEvents.h @@ -15,6 +15,7 @@ namespace winrt::TerminalApp::implementation til::typed_event TaskbarProgressChanged; til::typed_event ReadOnlyChanged; til::typed_event FocusRequested; + til::typed_event NotificationRequested; til::typed_event DispatchCommandRequested; }; diff --git a/src/cascadia/TerminalApp/DesktopNotification.cpp b/src/cascadia/TerminalApp/DesktopNotification.cpp new file mode 100644 index 0000000000..9d9294a1c5 --- /dev/null +++ b/src/cascadia/TerminalApp/DesktopNotification.cpp @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "DesktopNotification.h" + +using namespace winrt::Windows::UI::Notifications; +using namespace winrt::Windows::Data::Xml::Dom; + +namespace winrt::TerminalApp::implementation +{ + std::atomic DesktopNotification::_lastNotificationTime{ 0 }; + + // Method Description: + // - Rate-limits toast notifications so we don't spam the user. + // Return Value: + // - Returns true if a notification is allowed, false if too recent. + bool DesktopNotification::ShouldSendNotification() + { + FILETIME ft{}; + GetSystemTimeAsFileTime(&ft); + const auto now = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + auto last = _lastNotificationTime.load(std::memory_order_relaxed); + + if (now - last < MinNotificationIntervalTicks) + { + return false; + } + + // Attempt to update; if another thread beat us, that's fine — we'll skip this one. + return _lastNotificationTime.compare_exchange_strong(last, now, std::memory_order_relaxed); + } + + // Method Description: + // - Sends a toast notification with the given title and message. + // - When the user clicks the toast, the `Activated` callback fires + // with the tabIndex that was passed in, so the caller can switch + // to the correct tab and summon the window. + // Arguments: + // - args: The title, message, and tab index to include in the notification. + // - activated: A callback invoked on the background thread when the + // toast is clicked. The uint32_t parameter is the tab index. + void DesktopNotification::SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc) + { + try + { + if (!ShouldSendNotification()) + { + return; + } + + // Build the toast XML. We use a simple template with a title and body text. + // + // + // + // + // Title + // Message + // + // + // + auto toastXml = ToastNotificationManager::GetTemplateContent(ToastTemplateType::ToastText02); + auto textNodes = toastXml.GetElementsByTagName(L"text"); + + // First is the title + textNodes.Item(0).InnerText(args.Title); + // Second is the body + textNodes.Item(1).InnerText(args.Message); + + auto toastElement = toastXml.DocumentElement(); + + // When a toast is clicked, Windows launches a new instance of the app + // with the "launch" attribute as command-line arguments. We handle + // toast activation in-process via the Activated event below, so the + // new instance should do nothing. "__fromToast" is recognized by + // AppCommandlineArgs::ParseArgs as a no-op sentinel. + toastElement.SetAttribute(L"launch", L"__fromToast"); + + toastElement.SetAttribute(L"scenario", L"default"); + + auto toast = ToastNotification{ toastXml }; + + // Set the tag and group to enable notification replacement. + // Using the tab index as a tag means repeated output from the same tab + // replaces the previous notification rather than stacking. + toast.Tag(fmt::format(FMT_COMPILE(L"wt-tab-{}"), args.TabIndex)); + toast.Group(L"WindowsTerminal"); + + // When the user activates (clicks) the toast, fire the callback. + if (activatedFunc) + { + const auto tabIndex = args.TabIndex; + toast.Activated([activatedFunc, tabIndex](const auto& /*sender*/, const auto& /*eventArgs*/) { + activatedFunc(tabIndex); + }); + } + + // For packaged apps, CreateToastNotifier() uses the package identity automatically. + // For unpackaged apps, we need to provide an AUMID, but that case is less common + // and toast notifications may not be supported without additional setup. + auto notifier = ToastNotificationManager::CreateToastNotifier(); + notifier.Show(toast); + } + catch (...) + { + // Toast notification is a best-effort feature. If it fails (e.g., notifications + // are disabled, or the app is unpackaged without proper AUMID setup), we silently + // ignore the error. + LOG_CAUGHT_EXCEPTION(); + } + } +} diff --git a/src/cascadia/TerminalApp/DesktopNotification.h b/src/cascadia/TerminalApp/DesktopNotification.h new file mode 100644 index 0000000000..4b717e6479 --- /dev/null +++ b/src/cascadia/TerminalApp/DesktopNotification.h @@ -0,0 +1,38 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- DesktopNotification.h + +Module Description: +- Helper for sending Windows desktop toast notifications. Used to surface + terminal activity events to the user via the Windows notification center. +--*/ + +#pragma once +#include "pch.h" + +namespace winrt::TerminalApp::implementation +{ + struct DesktopNotificationArgs + { + winrt::hstring Title; + winrt::hstring Message; + uint32_t TabIndex{ 0 }; + }; + + class DesktopNotification + { + public: + static bool ShouldSendNotification(); + static void SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc); + + private: + static std::atomic _lastNotificationTime; + + // Minimum interval between notifications, in 100ns ticks (FILETIME units). + // 5 seconds = 5 * 10,000,000 + static constexpr int64_t MinNotificationIntervalTicks = 50'000'000LL; + }; +} diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index f4c6ce1395..8128776247 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -16,6 +16,12 @@ namespace TerminalApp Boolean FlashTaskbar { get; }; }; + runtimeclass NotificationEventArgs + { + String Title { get; }; + String Body { get; }; + }; + interface IPaneContent { Windows.UI.Xaml.FrameworkElement GetRoot(); @@ -46,6 +52,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler TaskbarProgressChanged; event Windows.Foundation.TypedEventHandler ReadOnlyChanged; event Windows.Foundation.TypedEventHandler FocusRequested; + event Windows.Foundation.TypedEventHandler NotificationRequested; }; diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 81ca7d51f8..937cb9c3f1 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -739,6 +739,18 @@ Windows This is displayed as a label for the context menu item that holds the submenu of available windows. + + Windows Terminal + Title shown in desktop toast notifications sent by the terminal. + + + Activity in tab "{0}" + {0} is the tab title. Shown as the body of a desktop notification when tab activity is detected. + + + Activity in tab "{0}" (window "{1}") + {0} is the tab title, {1} is the window name. Shown as the body of a desktop notification when tab activity is detected and the window has a name. + Open a new tab in given starting directory diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 4bbf58e50a..09fac9913a 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1166,6 +1166,26 @@ namespace winrt::TerminalApp::implementation events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &Tab::_bubbleRestartTerminalRequested }); } + events.NotificationRequested = content.NotificationRequested( + winrt::auto_revoke, + [dispatcher, weakThis](TerminalApp::IPaneContent /*sender*/, auto notifArgs) -> safe_void_coroutine { + const auto weakThisCopy = weakThis; + co_await wil::resume_foreground(dispatcher); + if (const auto tab{ weakThisCopy.get() }) + { + const auto notifTitle = notifArgs.Title(); + const auto notifBody = notifArgs.Body(); + if (!notifTitle.empty()) + { + tab->TabToastNotificationRequested.raise(notifTitle, notifBody, tab->TabViewIndex()); + } + else + { + tab->TabToastNotificationRequested.raise(tab->Title(), L"", tab->TabViewIndex()); + } + } + }); + if (_tabStatus.IsInputBroadcastActive()) { if (const auto& termContent{ content.try_as() }) diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 65d4e574ed..4235f0319e 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -121,6 +121,7 @@ namespace winrt::TerminalApp::implementation til::typed_event ActivePaneChanged; til::event> TabRaiseVisualBell; + til::event> TabToastNotificationRequested; til::typed_event TaskbarProgressChanged; // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. @@ -182,6 +183,7 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::IPaneContent::ConnectionStateChanged_revoker ConnectionStateChanged; winrt::TerminalApp::IPaneContent::ReadOnlyChanged_revoker ReadOnlyChanged; winrt::TerminalApp::IPaneContent::FocusRequested_revoker FocusRequested; + winrt::TerminalApp::IPaneContent::NotificationRequested_revoker NotificationRequested; // These events literally only apply if the content is a TermControl. winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent; diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 21e31fb6dd..342933785d 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -17,6 +17,7 @@ #include "TabRowControl.h" #include "DebugTapConnection.h" +#include "DesktopNotification.h" #include "..\TerminalSettingsModel\FileUtils.h" #include "../TerminalSettingsAppAdapterLib/TerminalSettings.h" @@ -150,6 +151,15 @@ namespace winrt::TerminalApp::implementation } }); + // When a tab requests a desktop toast notification, send the toast + // and handle activation by summoning this window and switching to the tab. + newTabImpl->TabToastNotificationRequested([weakThis{ get_weak() }](const winrt::hstring& title, const winrt::hstring& body, uint32_t tabIndex) { + if (const auto page{ weakThis.get() }) + { + page->_SendDesktopNotification(title, body, tabIndex); + } + }); + auto tabViewItem = newTabImpl->TabViewItem(); _tabView.TabItems().InsertAt(insertPosition, tabViewItem); @@ -1185,4 +1195,65 @@ namespace winrt::TerminalApp::implementation { return _tabs.Size() > 1; } + + // Method Description: + // - Sends a desktop toast notification with the given title and body. + // When the toast is activated (clicked), the window is summoned and + // the tab at tabIndex is focused. + // Arguments: + // - tabTitle: The title to display in the notification. + // - body: The body text. If empty, a standard tab-activity message is built. + // - tabIndex: The index of the tab to switch to when the toast is activated. + void TerminalPage::_SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, uint32_t tabIndex) + { + // Build the notification message. + // If a custom body is provided (e.g. from OSC 777), use the title/body directly. + // Otherwise, build the standard tab-activity notification message. + winrt::hstring notificationTitle; + winrt::hstring message; + if (!body.empty()) + { + notificationTitle = tabTitle; + message = body; + } + else + { + // Use the window name if available for context; otherwise just use the tab title. + // Use the raw WindowName (not WindowNameForDisplay) so we don't include + // the "" placeholder in the notification body. + const auto windowName = _WindowProperties ? _WindowProperties.WindowName() : winrt::hstring{}; + if (!windowName.empty()) + { + message = RS_fmt(L"NotificationMessage_TabActivityInWindow", std::wstring_view{ tabTitle }, std::wstring_view{ windowName }); + } + else + { + message = RS_fmt(L"NotificationMessage_TabActivity", std::wstring_view{ tabTitle }); + } + notificationTitle = RS_(L"NotificationTitle"); + } + + implementation::DesktopNotificationArgs args; + args.Title = notificationTitle; + args.Message = message; + args.TabIndex = tabIndex; + + implementation::DesktopNotification::SendNotification(args, [weakThis{ get_weak() }](uint32_t idx) { + if (const auto page{ weakThis.get() }) + { + // The toast Activated callback runs on a background thread. + // Marshal both the summon and tab-switch to the UI thread. + page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, idx]() { + if (const auto p{ weakPage.get() }) + { + p->SummonWindowRequested.raise(nullptr, nullptr); + if (idx < p->_tabs.Size()) + { + p->_SelectTab(idx); + } + } + }); + } + }); + } } diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index fe141dfa64..371dbd1746 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -174,6 +174,7 @@ TerminalPaneContent.idl + SuggestionsControl.xaml @@ -287,6 +288,7 @@ + SuggestionsControl.xaml diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 4b48cc0e9d..bd1906f1fe 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -570,6 +570,8 @@ namespace winrt::TerminalApp::implementation void _activePaneChanged(winrt::TerminalApp::Tab tab, Windows::Foundation::IInspectable args); safe_void_coroutine _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); + void _SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, uint32_t tabIndex); + #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp #define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index 0e828cdc1b..b2f38ba249 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -4,6 +4,7 @@ #pragma once #include "TerminalPaneContent.g.h" #include "BellEventArgs.g.h" +#include "NotificationEventArgs.g.h" #include "BasicPaneEvents.h" namespace winrt::TerminalApp::implementation @@ -19,6 +20,16 @@ namespace winrt::TerminalApp::implementation til::property FlashTaskbar; }; + struct NotificationEventArgs : public NotificationEventArgsT + { + public: + NotificationEventArgs(const winrt::hstring& title = {}, const winrt::hstring& body = {}) : + Title(title), Body(body) {} + + til::property Title; + til::property Body; + }; + struct TerminalPaneContent : TerminalPaneContentT, BasicPaneEvents { TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index ee36db25e3..ba0dcfda26 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -54,6 +54,9 @@ #include #include +#include +#include + #include #include #include From c23e507c2074948900c508b3e2c070a473636ccb Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Mar 2026 15:40:13 -0700 Subject: [PATCH 02/24] spell --- .github/actions/spelling/expect/expect.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 6c226753b9..9b39f26811 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1135,6 +1135,9 @@ NOSIZE NOSNAPSHOT NOTHOUSANDS NOTICKS +notifArgs +notifBody +notifTitle NOTIMEOUTIFNOTHUNG NOTIMPL NOTOPMOST From aeb531f666a25206e96ecce74fc64ea02fe3a57a Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Mar 2026 16:00:51 -0700 Subject: [PATCH 03/24] spell; prefer AppDisplayName; use C++20 designated init --- .github/actions/spelling/expect/expect.txt | 4 +--- .../TerminalApp/Resources/en-US/Resources.resw | 4 ---- src/cascadia/TerminalApp/TabManagement.cpp | 11 ++++++----- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 9b39f26811..d5edc8ce0d 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1135,9 +1135,7 @@ NOSIZE NOSNAPSHOT NOTHOUSANDS NOTICKS -notifArgs -notifBody -notifTitle +notif NOTIMEOUTIFNOTHUNG NOTIMPL NOTOPMOST diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 937cb9c3f1..951dc6eff0 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -739,10 +739,6 @@ Windows This is displayed as a label for the context menu item that holds the submenu of available windows. - - Windows Terminal - Title shown in desktop toast notifications sent by the terminal. - Activity in tab "{0}" {0} is the tab title. Shown as the body of a desktop notification when tab activity is detected. diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 342933785d..6cb06f2e30 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -1230,13 +1230,14 @@ namespace winrt::TerminalApp::implementation { message = RS_fmt(L"NotificationMessage_TabActivity", std::wstring_view{ tabTitle }); } - notificationTitle = RS_(L"NotificationTitle"); + notificationTitle = CascadiaSettings::ApplicationDisplayName(); } - implementation::DesktopNotificationArgs args; - args.Title = notificationTitle; - args.Message = message; - args.TabIndex = tabIndex; + const implementation::DesktopNotificationArgs args{ + .Title = notificationTitle, + .Message = message, + .TabIndex = tabIndex, + }; implementation::DesktopNotification::SendNotification(args, [weakThis{ get_weak() }](uint32_t idx) { if (const auto page{ weakThis.get() }) From 9df166efec215d2f61459cc55fdcad86482f031a Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 25 Mar 2026 13:27:12 -0700 Subject: [PATCH 04/24] Send notifications even when we're unpackaged (#20013) ## Summary of the Pull Request Targets #20010 Manually assign an AUMID to our process when we're running unpackaged. Main difference from #19937 is what AUMID we use. Before, it was per branding, but the `WindowEmperor` already appends an exe path hash for unpackaged instances to prevent crosstalk. Here, we're just using the same pattern: `Microsoft.WindowsTerminal.`. Heavily based on #19937 Co-authored by @zadjii-msft --- .../TerminalApp/DesktopNotification.cpp | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/DesktopNotification.cpp b/src/cascadia/TerminalApp/DesktopNotification.cpp index 9d9294a1c5..50214cdbf7 100644 --- a/src/cascadia/TerminalApp/DesktopNotification.cpp +++ b/src/cascadia/TerminalApp/DesktopNotification.cpp @@ -4,6 +4,8 @@ #include "pch.h" #include "DesktopNotification.h" +#include + using namespace winrt::Windows::UI::Notifications; using namespace winrt::Windows::Data::Xml::Dom; @@ -96,10 +98,26 @@ namespace winrt::TerminalApp::implementation } // For packaged apps, CreateToastNotifier() uses the package identity automatically. - // For unpackaged apps, we need to provide an AUMID, but that case is less common - // and toast notifications may not be supported without additional setup. - auto notifier = ToastNotificationManager::CreateToastNotifier(); - notifier.Show(toast); + // For unpackaged apps, we must pass the explicit AUMID that was registered + // at startup via SetCurrentProcessExplicitAppUserModelID. + winrt::Windows::UI::Notifications::ToastNotifier notifier{ nullptr }; + if (IsPackaged()) + { + notifier = ToastNotificationManager::CreateToastNotifier(); + } + else + { + // Retrieve the AUMID that was set by WindowEmperor at startup. + wil::unique_cotaskmem_string aumid; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&aumid))) + { + notifier = ToastNotificationManager::CreateToastNotifier(aumid.get()); + } + } + if (notifier) + { + notifier.Show(toast); + } } catch (...) { From 5f3db13e5f33a764ab25b830052e0b724f687e1d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 26 Mar 2026 18:12:08 -0700 Subject: [PATCH 05/24] fix extra window and reorder/glom scenarios --- .../TerminalApp/DesktopNotification.cpp | 13 ++-- .../TerminalApp/DesktopNotification.h | 4 +- src/cascadia/TerminalApp/Tab.cpp | 12 +--- src/cascadia/TerminalApp/Tab.h | 2 +- src/cascadia/TerminalApp/TabManagement.cpp | 64 +++++++++++++++---- src/cascadia/TerminalApp/TerminalPage.h | 4 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 9 +++ src/cascadia/TerminalApp/TerminalWindow.h | 2 + src/cascadia/TerminalApp/TerminalWindow.idl | 3 + src/cascadia/WindowsTerminal/AppHost.cpp | 9 +++ src/cascadia/WindowsTerminal/AppHost.h | 4 ++ .../WindowsTerminal/WindowEmperor.cpp | 28 +++++++- src/cascadia/WindowsTerminal/WindowEmperor.h | 1 + 13 files changed, 121 insertions(+), 34 deletions(-) diff --git a/src/cascadia/TerminalApp/DesktopNotification.cpp b/src/cascadia/TerminalApp/DesktopNotification.cpp index 50214cdbf7..fb33d1a84d 100644 --- a/src/cascadia/TerminalApp/DesktopNotification.cpp +++ b/src/cascadia/TerminalApp/DesktopNotification.cpp @@ -42,7 +42,7 @@ namespace winrt::TerminalApp::implementation // - args: The title, message, and tab index to include in the notification. // - activated: A callback invoked on the background thread when the // toast is clicked. The uint32_t parameter is the tab index. - void DesktopNotification::SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc) + void DesktopNotification::SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc) { try { @@ -83,17 +83,16 @@ namespace winrt::TerminalApp::implementation auto toast = ToastNotification{ toastXml }; // Set the tag and group to enable notification replacement. - // Using the tab index as a tag means repeated output from the same tab - // replaces the previous notification rather than stacking. - toast.Tag(fmt::format(FMT_COMPILE(L"wt-tab-{}"), args.TabIndex)); + // Repeated notifications with the same tag replace the previous one + // rather than stacking in the notification center. + toast.Tag(args.Tag); toast.Group(L"WindowsTerminal"); // When the user activates (clicks) the toast, fire the callback. if (activatedFunc) { - const auto tabIndex = args.TabIndex; - toast.Activated([activatedFunc, tabIndex](const auto& /*sender*/, const auto& /*eventArgs*/) { - activatedFunc(tabIndex); + toast.Activated([activatedFunc](const auto& /*sender*/, const auto& /*eventArgs*/) { + activatedFunc(); }); } diff --git a/src/cascadia/TerminalApp/DesktopNotification.h b/src/cascadia/TerminalApp/DesktopNotification.h index 4b717e6479..273fad18b8 100644 --- a/src/cascadia/TerminalApp/DesktopNotification.h +++ b/src/cascadia/TerminalApp/DesktopNotification.h @@ -19,14 +19,14 @@ namespace winrt::TerminalApp::implementation { winrt::hstring Title; winrt::hstring Message; - uint32_t TabIndex{ 0 }; + winrt::hstring Tag; }; class DesktopNotification { public: static bool ShouldSendNotification(); - static void SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc); + static void SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc); private: static std::atomic _lastNotificationTime; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 09fac9913a..cd236ee452 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1173,16 +1173,8 @@ namespace winrt::TerminalApp::implementation co_await wil::resume_foreground(dispatcher); if (const auto tab{ weakThisCopy.get() }) { - const auto notifTitle = notifArgs.Title(); - const auto notifBody = notifArgs.Body(); - if (!notifTitle.empty()) - { - tab->TabToastNotificationRequested.raise(notifTitle, notifBody, tab->TabViewIndex()); - } - else - { - tab->TabToastNotificationRequested.raise(tab->Title(), L"", tab->TabViewIndex()); - } + const auto title = notifArgs.Title().empty() ? tab->Title() : notifArgs.Title(); + tab->TabToastNotificationRequested.raise(title, notifArgs.Body()); } }); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 4235f0319e..7e90eab752 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -121,7 +121,7 @@ namespace winrt::TerminalApp::implementation til::typed_event ActivePaneChanged; til::event> TabRaiseVisualBell; - til::event> TabToastNotificationRequested; + til::event> TabToastNotificationRequested; til::typed_event TaskbarProgressChanged; // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 6cb06f2e30..1608c62efa 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -153,10 +153,13 @@ namespace winrt::TerminalApp::implementation // When a tab requests a desktop toast notification, send the toast // and handle activation by summoning this window and switching to the tab. - newTabImpl->TabToastNotificationRequested([weakThis{ get_weak() }](const winrt::hstring& title, const winrt::hstring& body, uint32_t tabIndex) { + newTabImpl->TabToastNotificationRequested([weakThis{ get_weak() }, weakTab{ newTabImpl->get_weak() }](const winrt::hstring& title, const winrt::hstring& body) { if (const auto page{ weakThis.get() }) { - page->_SendDesktopNotification(title, body, tabIndex); + if (const auto tab{ weakTab.get() }) + { + page->_SendDesktopNotification(title, body, tab); + } } }); @@ -1196,15 +1199,31 @@ namespace winrt::TerminalApp::implementation return _tabs.Size() > 1; } + // Method Description: + // - Attempts to find and focus the given tab in this window. + // Arguments: + // - tab: The tab to focus. + // Return Value: + // - true if the tab was found and focused, false otherwise. + bool TerminalPage::FocusTab(const winrt::TerminalApp::Tab& tab) + { + if (const auto tabIndex{ _GetTabIndex(tab) }) + { + _SelectTab(tabIndex.value()); + return true; + } + return false; + } + // Method Description: // - Sends a desktop toast notification with the given title and body. // When the toast is activated (clicked), the window is summoned and - // the tab at tabIndex is focused. + // the originating tab is focused. // Arguments: // - tabTitle: The title to display in the notification. // - body: The body text. If empty, a standard tab-activity message is built. - // - tabIndex: The index of the tab to switch to when the toast is activated. - void TerminalPage::_SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, uint32_t tabIndex) + // - tab: The tab to switch to when the toast is activated. + void TerminalPage::_SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, const winrt::com_ptr& tab) { // Build the notification message. // If a custom body is provided (e.g. from OSC 777), use the title/body directly. @@ -1233,24 +1252,45 @@ namespace winrt::TerminalApp::implementation notificationTitle = CascadiaSettings::ApplicationDisplayName(); } + // Use the Tab object's identity hash as a stable toast tag. + // This survives tab reordering and cross-window moves. + const auto tabHash = std::hash{}(*tab); + const hstring tabTag{ fmt::format(FMT_COMPILE(L"wt-tab-{:016x}"), tabHash) }; + const implementation::DesktopNotificationArgs args{ .Title = notificationTitle, .Message = message, - .TabIndex = tabIndex, + .Tag = tabTag }; - implementation::DesktopNotification::SendNotification(args, [weakThis{ get_weak() }](uint32_t idx) { + implementation::DesktopNotification::SendNotification(args, [weakThis{ get_weak() }, weakTab{ tab->get_weak() }]() { if (const auto page{ weakThis.get() }) { // The toast Activated callback runs on a background thread. - // Marshal both the summon and tab-switch to the UI thread. - page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, idx]() { + // Marshal to the UI thread for tab focus and window summon. + page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakTab]() { if (const auto p{ weakPage.get() }) { - p->SummonWindowRequested.raise(nullptr, nullptr); - if (idx < p->_tabs.Size()) + if (const auto t{ weakTab.get() }) { - p->_SelectTab(idx); + // Try to find and focus the tab in this window first. + if (const auto tabIndex{ p->_GetTabIndex(*t) }) + { + p->SummonWindowRequested.raise(nullptr, nullptr); + p->_SelectTab(tabIndex.value()); + } + else + { + // The tab may have moved to another window. + // Raise FocusTabRequested so the emperor can + // search all windows for it. + p->FocusTabRequested.raise(nullptr, *t); + } + } + else + { + // Tab was closed. Just summon this window. + p->SummonWindowRequested.raise(nullptr, nullptr); } } }); diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index bd1906f1fe..a43ba1627d 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -168,6 +168,7 @@ namespace winrt::TerminalApp::implementation void OpenSettingsUI(); void WindowActivated(const bool activated); + bool FocusTab(const winrt::TerminalApp::Tab& tab); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); @@ -192,6 +193,7 @@ namespace winrt::TerminalApp::implementation til::typed_event IdentifyWindowsRequested; til::typed_event RenameWindowRequested; til::typed_event SummonWindowRequested; + til::typed_event FocusTabRequested; til::typed_event WindowSizeChanged; til::typed_event OpenSystemMenu; @@ -570,7 +572,7 @@ namespace winrt::TerminalApp::implementation void _activePaneChanged(winrt::TerminalApp::Tab tab, Windows::Foundation::IInspectable args); safe_void_coroutine _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); - void _SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, uint32_t tabIndex); + void _SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, const winrt::com_ptr& tab); #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 266ece81db..016bbb5bee 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -1205,6 +1205,15 @@ namespace winrt::TerminalApp::implementation } } + bool TerminalWindow::FocusTab(const winrt::TerminalApp::Tab& tab) + { + if (_root) + { + return _root->FocusTab(tab); + } + return false; + } + void TerminalWindow::WindowName(const winrt::hstring& name) { const auto oldIsQuakeMode = _WindowProperties->IsQuakeWindow(); diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 2f1aad5a7a..08ddeabafc 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -92,6 +92,7 @@ namespace winrt::TerminalApp::implementation bool ShowTabsFullscreen() const; bool AutoHideWindow(); void IdentifyWindow(); + bool FocusTab(const winrt::TerminalApp::Tab& tab); std::optional LoadPersistedLayoutIdx() const; winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout(); @@ -221,6 +222,7 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested); FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested); + FORWARDED_TYPED_EVENT(FocusTabRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::Tab, _root, FocusTabRequested); FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu); FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested); FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged); diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index b900522cbf..c12f73e137 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -4,6 +4,7 @@ import "IPaneContent.idl"; import "TerminalPage.idl"; import "ShortcutActionDispatch.idl"; +import "Tab.idl"; namespace TerminalApp { @@ -74,6 +75,7 @@ namespace TerminalApp Boolean ShowTabsFullscreen { get; }; void IdentifyWindow(); + Boolean FocusTab(TerminalApp.Tab tab); void SetPersistedLayoutIdx(UInt32 idx); void RequestExitFullscreen(); @@ -126,6 +128,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; event Windows.Foundation.TypedEventHandler SummonWindowRequested; + event Windows.Foundation.TypedEventHandler FocusTabRequested; event Windows.Foundation.TypedEventHandler OpenSystemMenu; event Windows.Foundation.TypedEventHandler QuitRequested; event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 522e90c7d2..0fa2f61ae4 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -265,6 +265,7 @@ void AppHost::Initialize() _revokers.IsQuakeWindowChanged = _windowLogic.IsQuakeWindowChanged(winrt::auto_revoke, { this, &AppHost::_IsQuakeWindowChanged }); _revokers.SummonWindowRequested = _windowLogic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested }); + _revokers.FocusTabRequested = _windowLogic.FocusTabRequested(winrt::auto_revoke, { this, &AppHost::_FocusTabRequested }); _revokers.OpenSystemMenu = _windowLogic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu }); _revokers.QuitRequested = _windowLogic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll }); _revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged }); @@ -1064,6 +1065,14 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta HandleSummon(std::move(summonArgs)); } +void AppHost::_FocusTabRequested(const winrt::Windows::Foundation::IInspectable&, + const winrt::TerminalApp::Tab& tab) +{ + // The tab may have moved to another window. Ask the emperor to + // search all windows and focus the tab wherever it currently lives. + _windowManager->FocusTabInAnyWindow(tab); +} + void AppHost::_OpenSystemMenu(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&) { diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 379f876b94..ffc16d916b 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -91,6 +91,9 @@ private: void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _FocusTabRequested(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::TerminalApp::Tab& tab); + void _OpenSystemMenu(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); @@ -151,6 +154,7 @@ private: winrt::TerminalApp::TerminalWindow::IdentifyWindowsRequested_revoker IdentifyWindowsRequested; winrt::TerminalApp::TerminalWindow::IsQuakeWindowChanged_revoker IsQuakeWindowChanged; winrt::TerminalApp::TerminalWindow::SummonWindowRequested_revoker SummonWindowRequested; + winrt::TerminalApp::TerminalWindow::FocusTabRequested_revoker FocusTabRequested; winrt::TerminalApp::TerminalWindow::OpenSystemMenu_revoker OpenSystemMenu; winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested; winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 07e96fcc2e..a7e72c70b3 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -751,6 +751,25 @@ bool WindowEmperor::_summonWindow(const SummonWindowSelectionArgs& args) const return true; } +void WindowEmperor::FocusTabInAnyWindow(const winrt::TerminalApp::Tab& tab) const +{ + _assertIsMainThread(); + + for (const auto& w : _windows) + { + if (w->Logic().FocusTab(tab)) + { + winrt::TerminalApp::SummonWindowBehavior summonArgs; + summonArgs.MoveToCurrentDesktop(false); + summonArgs.DropdownDuration(0); + summonArgs.ToMonitor(winrt::TerminalApp::MonitorBehavior::InPlace); + summonArgs.ToggleVisibility(false); + w->HandleSummon(std::move(summonArgs)); + return; + } + } +} + void WindowEmperor::_summonAllWindows() const { _assertIsMainThread(); @@ -1009,7 +1028,14 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c { const auto handoff = deserializeHandoffPayload(static_cast(cds->lpData), static_cast(cds->lpData) + cds->cbData); const auto argv = commandlineToArgArray(handoff.args.c_str()); - _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); + // When a toast notification is clicked, Windows launches a new + // wt.exe with "__fromToast". That instance hands off here via + // WM_COPYDATA. We already handle activation in-process via the + // toast's Activated event, so just ignore this handoff. + if (argv.size() != 2 || argv[1] != L"__fromToast") + { + _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); + } } return 0; case WM_HOTKEY: diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 5e2801276c..42c84c38c9 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -35,6 +35,7 @@ public: AppHost* GetWindowByName(std::wstring_view name) const noexcept; void CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args); void HandleCommandlineArgs(int nCmdShow); + void FocusTabInAnyWindow(const winrt::TerminalApp::Tab& tab) const; private: struct SummonWindowSelectionArgs From b53ccb853e258a0545361e20f52e44008bad629d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 16 Apr 2026 17:58:58 -0700 Subject: [PATCH 06/24] Suppress notification if focused; add pane data --- src/cascadia/TerminalApp/Tab.cpp | 4 +-- src/cascadia/TerminalApp/Tab.h | 2 +- src/cascadia/TerminalApp/TabManagement.cpp | 35 ++++++++++++++++++---- src/cascadia/TerminalApp/TerminalPage.h | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index cd236ee452..20f5d10ac6 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1168,13 +1168,13 @@ namespace winrt::TerminalApp::implementation events.NotificationRequested = content.NotificationRequested( winrt::auto_revoke, - [dispatcher, weakThis](TerminalApp::IPaneContent /*sender*/, auto notifArgs) -> safe_void_coroutine { + [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto notifArgs) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (const auto tab{ weakThisCopy.get() }) { const auto title = notifArgs.Title().empty() ? tab->Title() : notifArgs.Title(); - tab->TabToastNotificationRequested.raise(title, notifArgs.Body()); + tab->TabToastNotificationRequested.raise(title, notifArgs.Body(), sender); } }); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 7e90eab752..3241ae23aa 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -121,7 +121,7 @@ namespace winrt::TerminalApp::implementation til::typed_event ActivePaneChanged; til::event> TabRaiseVisualBell; - til::event> TabToastNotificationRequested; + til::event> TabToastNotificationRequested; til::typed_event TaskbarProgressChanged; // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 1608c62efa..9c2621f712 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -153,12 +153,12 @@ namespace winrt::TerminalApp::implementation // When a tab requests a desktop toast notification, send the toast // and handle activation by summoning this window and switching to the tab. - newTabImpl->TabToastNotificationRequested([weakThis{ get_weak() }, weakTab{ newTabImpl->get_weak() }](const winrt::hstring& title, const winrt::hstring& body) { + newTabImpl->TabToastNotificationRequested([weakThis{ get_weak() }, weakTab{ newTabImpl->get_weak() }](const winrt::hstring& title, const winrt::hstring& body, const winrt::TerminalApp::IPaneContent& content) { if (const auto page{ weakThis.get() }) { if (const auto tab{ weakTab.get() }) { - page->_SendDesktopNotification(title, body, tab); + page->_SendDesktopNotification(title, body, tab, content); } } }); @@ -1223,8 +1223,21 @@ namespace winrt::TerminalApp::implementation // - tabTitle: The title to display in the notification. // - body: The body text. If empty, a standard tab-activity message is built. // - tab: The tab to switch to when the toast is activated. - void TerminalPage::_SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, const winrt::com_ptr& tab) + void TerminalPage::_SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, const winrt::com_ptr& tab, const winrt::TerminalApp::IPaneContent& content) { + // Don't send a notification if the window is focused and the requesting + // pane is the active pane. The user is already looking at it. + if (_activated && tab == _GetFocusedTabImpl()) + { + if (const auto activePane{ tab->GetActivePane() }) + { + if (activePane->GetContent() == content) + { + return; + } + } + } + // Build the notification message. // If a custom body is provided (e.g. from OSC 777), use the title/body directly. // Otherwise, build the standard tab-activity notification message. @@ -1263,12 +1276,12 @@ namespace winrt::TerminalApp::implementation .Tag = tabTag }; - implementation::DesktopNotification::SendNotification(args, [weakThis{ get_weak() }, weakTab{ tab->get_weak() }]() { + implementation::DesktopNotification::SendNotification(args, [weakThis{ get_weak() }, weakTab{ tab->get_weak() }, weakContent{ winrt::make_weak(content) }]() { if (const auto page{ weakThis.get() }) { // The toast Activated callback runs on a background thread. // Marshal to the UI thread for tab focus and window summon. - page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakTab]() { + page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakTab, weakContent]() { if (const auto p{ weakPage.get() }) { if (const auto t{ weakTab.get() }) @@ -1278,6 +1291,18 @@ namespace winrt::TerminalApp::implementation { p->SummonWindowRequested.raise(nullptr, nullptr); p->_SelectTab(tabIndex.value()); + + // Focus the specific pane that raised the notification. + if (const auto paneContent{ weakContent.get() }) + { + const auto rootPane = t->GetRootPane(); + rootPane->WalkTree([&](const auto& pane) { + if (pane->GetContent() == paneContent) + { + rootPane->FocusPane(pane); + } + }); + } } else { diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index a43ba1627d..a4ea57b534 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -572,7 +572,7 @@ namespace winrt::TerminalApp::implementation void _activePaneChanged(winrt::TerminalApp::Tab tab, Windows::Foundation::IInspectable args); safe_void_coroutine _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); - void _SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, const winrt::com_ptr& tab); + void _SendDesktopNotification(const winrt::hstring& tabTitle, const winrt::hstring& body, const winrt::com_ptr& tab, const winrt::TerminalApp::IPaneContent& content); #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp From 54009e130542caaf7a74ffb8f44fc604c56c19eb Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 29 Apr 2026 14:40:33 -0700 Subject: [PATCH 07/24] --from-toast & GetTickCount64() --- src/cascadia/TerminalApp/AppCommandlineArgs.cpp | 4 ++-- src/cascadia/TerminalApp/DesktopNotification.cpp | 16 ++++++++-------- src/cascadia/TerminalApp/DesktopNotification.h | 7 +++---- src/cascadia/WindowsTerminal/WindowEmperor.cpp | 4 ++-- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index a7ae09a01f..950984019b 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -1069,10 +1069,10 @@ int AppCommandlineArgs::ParseArgs(winrt::array_view args) } // When a toast notification is clicked, Windows may launch a new instance - // with "__fromToast" as the argument. This is a no-op sentinel — the + // with "--from-toast" as the argument. This is a no-op sentinel — the // in-process Activated handler on the toast already handled activation. // See DesktopNotification.cpp for more details. - if (args.size() == 2 && args[1] == L"__fromToast") + if (args.size() == 2 && args[1] == L"--from-toast") { return 0; } diff --git a/src/cascadia/TerminalApp/DesktopNotification.cpp b/src/cascadia/TerminalApp/DesktopNotification.cpp index fb33d1a84d..d1e8a198c7 100644 --- a/src/cascadia/TerminalApp/DesktopNotification.cpp +++ b/src/cascadia/TerminalApp/DesktopNotification.cpp @@ -11,7 +11,7 @@ using namespace winrt::Windows::Data::Xml::Dom; namespace winrt::TerminalApp::implementation { - std::atomic DesktopNotification::_lastNotificationTime{ 0 }; + std::atomic DesktopNotification::_lastNotificationTime{ 0 }; // Method Description: // - Rate-limits toast notifications so we don't spam the user. @@ -19,12 +19,12 @@ namespace winrt::TerminalApp::implementation // - Returns true if a notification is allowed, false if too recent. bool DesktopNotification::ShouldSendNotification() { - FILETIME ft{}; - GetSystemTimeAsFileTime(&ft); - const auto now = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + const auto now = GetTickCount64(); auto last = _lastNotificationTime.load(std::memory_order_relaxed); - if (now - last < MinNotificationIntervalTicks) + // Subtraction wraps cleanly modulo 2^64, so the delta is correct even + // across the (~584 million year) GetTickCount64 rollover. + if (now - last < MinNotificationIntervalMs) { return false; } @@ -53,7 +53,7 @@ namespace winrt::TerminalApp::implementation // Build the toast XML. We use a simple template with a title and body text. // - // + // // // // Title @@ -74,9 +74,9 @@ namespace winrt::TerminalApp::implementation // When a toast is clicked, Windows launches a new instance of the app // with the "launch" attribute as command-line arguments. We handle // toast activation in-process via the Activated event below, so the - // new instance should do nothing. "__fromToast" is recognized by + // new instance should do nothing. "--from-toast" is recognized by // AppCommandlineArgs::ParseArgs as a no-op sentinel. - toastElement.SetAttribute(L"launch", L"__fromToast"); + toastElement.SetAttribute(L"launch", L"--from-toast"); toastElement.SetAttribute(L"scenario", L"default"); diff --git a/src/cascadia/TerminalApp/DesktopNotification.h b/src/cascadia/TerminalApp/DesktopNotification.h index 273fad18b8..a3bab6dddd 100644 --- a/src/cascadia/TerminalApp/DesktopNotification.h +++ b/src/cascadia/TerminalApp/DesktopNotification.h @@ -29,10 +29,9 @@ namespace winrt::TerminalApp::implementation static void SendNotification(const DesktopNotificationArgs& args, std::function activatedFunc); private: - static std::atomic _lastNotificationTime; + static std::atomic _lastNotificationTime; - // Minimum interval between notifications, in 100ns ticks (FILETIME units). - // 5 seconds = 5 * 10,000,000 - static constexpr int64_t MinNotificationIntervalTicks = 50'000'000LL; + // Minimum interval between notifications, in milliseconds (GetTickCount64 units). + static constexpr uint64_t MinNotificationIntervalMs = 5'000; }; } diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index a7e72c70b3..7000abc2d2 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -1029,10 +1029,10 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c const auto handoff = deserializeHandoffPayload(static_cast(cds->lpData), static_cast(cds->lpData) + cds->cbData); const auto argv = commandlineToArgArray(handoff.args.c_str()); // When a toast notification is clicked, Windows launches a new - // wt.exe with "__fromToast". That instance hands off here via + // wt.exe with "--from-toast". That instance hands off here via // WM_COPYDATA. We already handle activation in-process via the // toast's Activated event, so just ignore this handoff. - if (argv.size() != 2 || argv[1] != L"__fromToast") + if (argv.size() != 2 || argv[1] != L"--from-toast") { _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show); } From 15ce2fd5132c9888e7e0e32af19a157addb3bc0f Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 24 Mar 2026 19:31:15 -0700 Subject: [PATCH 08/24] Add settings for "notifying" on "activity" --- doc/cascadia/profiles.schema.json | 72 +++++++ src/cascadia/TerminalApp/IPaneContent.idl | 2 + src/cascadia/TerminalApp/Tab.cpp | 76 +++++++- src/cascadia/TerminalApp/Tab.h | 6 + .../TerminalApp/TabHeaderControl.xaml | 6 + .../TerminalApp/TerminalPaneContent.cpp | 109 +++++++++-- .../TerminalApp/TerminalPaneContent.h | 18 +- .../TerminalApp/TerminalPaneContent.idl | 1 + src/cascadia/TerminalApp/TerminalTabStatus.h | 1 + .../TerminalApp/TerminalTabStatus.idl | 1 + src/cascadia/TerminalControl/ControlCore.cpp | 42 ++++- src/cascadia/TerminalControl/ControlCore.h | 7 +- src/cascadia/TerminalControl/ControlCore.idl | 2 + .../TerminalControl/IControlSettings.idl | 21 +++ src/cascadia/TerminalControl/TermControl.cpp | 13 ++ src/cascadia/TerminalControl/TermControl.h | 7 + src/cascadia/TerminalControl/TermControl.idl | 3 + src/cascadia/TerminalCore/Terminal.cpp | 15 ++ src/cascadia/TerminalCore/Terminal.hpp | 6 +- src/cascadia/TerminalCore/TerminalApi.cpp | 20 +- .../TerminalSettings.cpp | 3 + .../ProfileViewModel.cpp | 177 ++++++++++++++++++ .../TerminalSettingsEditor/ProfileViewModel.h | 20 ++ .../ProfileViewModel.idl | 20 ++ .../Profiles_Advanced.xaml | 52 +++++ .../Resources/en-US/Resources.resw | 60 ++++++ .../TerminalSettingsModel/EnumMappings.cpp | 1 + .../TerminalSettingsModel/EnumMappings.h | 1 + .../TerminalSettingsModel/EnumMappings.idl | 1 + .../TerminalSettingsModel/MTSMSettings.h | 65 ++++--- .../TerminalSettingsModel/Profile.idl | 3 + .../TerminalSettingsSerializationHelpers.h | 40 ++++ src/cascadia/inc/ControlProperties.h | 59 +++--- src/host/outputStream.cpp | 2 +- src/host/outputStream.hpp | 2 +- src/terminal/adapter/ITerminalApi.hpp | 11 +- src/terminal/adapter/adaptDispatch.cpp | 12 +- 37 files changed, 866 insertions(+), 91 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 11eb698965..ce6f612f5e 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2849,6 +2849,78 @@ "description": "Sets the sound played when the application emits a BEL. When set to an array, the terminal will pick one of those sounds at random.", "$ref": "#/$defs/BellSound" }, + "notifyOnInactiveOutput": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "taskbar", + "audible", + "tab", + "notification" + ] + } + }, + { + "type": "string", + "enum": [ + "taskbar", + "audible", + "tab", + "notification", + "all", + "none" + ] + } + ], + "description": "Controls how you are notified when an inactive tab produces new output." + }, + "notifyOnNextPrompt": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "taskbar", + "audible", + "tab", + "notification" + ] + } + }, + { + "type": "string", + "enum": [ + "taskbar", + "audible", + "tab", + "notification", + "all", + "none" + ] + } + ], + "description": "Controls how you are notified when a new shell prompt is detected. Requires shell integration." + }, + "autoDetectRunningCommand": { + "default": "disabled", + "description": "Automatically detect when a command is running and show a progress indicator in the tab and taskbar.", + "enum": [ + "disabled", + "automatic", + "progress" + ], + "type": "string" + }, "closeOnExit": { "default": "automatic", "description": "Sets how the profile reacts to termination or failure to launch. Possible values:\n -\"graceful\" (close when exit is typed or the process exits normally)\n -\"always\" (always close)\n -\"automatic\" (behave as \"graceful\" only for processes launched by terminal, behave as \"always\" otherwise)\n -\"never\" (never close).\ntrue and false are accepted as synonyms for \"graceful\" and \"never\" respectively.", diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index 8128776247..605e377cdf 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -18,6 +18,8 @@ namespace TerminalApp runtimeclass NotificationEventArgs { + Microsoft.Terminal.Control.OutputNotificationStyle Style { get; }; + Boolean OnlyWhenInactive { get; }; String Title { get; }; String Body { get; }; }; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 236ab16382..2c5ae5e9cc 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -123,6 +123,12 @@ namespace winrt::TerminalApp::implementation _bellIndicatorTimer.Stop(); } + void Tab::_ActivityIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/) + { + ShowActivityIndicator(false); + _activityIndicatorTimer.Stop(); + } + // Method Description: // - Initializes a TabViewItem for this Tab instance. // Arguments: @@ -329,6 +335,10 @@ namespace winrt::TerminalApp::implementation { ShowBellIndicator(false); } + if (_tabStatus.ActivityIndicator()) + { + ShowActivityIndicator(false); + } } } @@ -459,6 +469,26 @@ namespace winrt::TerminalApp::implementation _bellIndicatorTimer.Start(); } + void Tab::ShowActivityIndicator(const bool show) + { + ASSERT_UI_THREAD(); + + _tabStatus.ActivityIndicator(show); + } + + void Tab::ActivateActivityIndicatorTimer() + { + ASSERT_UI_THREAD(); + + if (!_activityIndicatorTimer) + { + _activityIndicatorTimer.Interval(std::chrono::milliseconds(2000)); + _activityIndicatorTimer.Tick({ get_weak(), &Tab::_ActivityIndicatorTimerTick }); + } + + _activityIndicatorTimer.Start(); + } + // Method Description: // - Gets the title string of the last focused terminal control in our tree. // Returns the empty string if there is no such control. @@ -1068,6 +1098,7 @@ namespace winrt::TerminalApp::implementation dispatcher, til::throttled_func_options{ .delay = std::chrono::milliseconds{ 200 }, + .leading = true, .trailing = true, }, [weakThis]() { @@ -1173,8 +1204,45 @@ namespace winrt::TerminalApp::implementation co_await wil::resume_foreground(dispatcher); if (const auto tab{ weakThisCopy.get() }) { - const auto title = notifArgs.Title().empty() ? tab->Title() : notifArgs.Title(); - tab->TabToastNotificationRequested.raise(title, notifArgs.Body(), sender); + const auto activeContent = tab->GetActiveContent(); + const auto isActivePaneContent = activeContent && activeContent == sender; + + if (notifArgs.OnlyWhenInactive() && isActivePaneContent && + tab->_focusState != WUX::FocusState::Unfocused) + { + co_return; + } + + const auto style = notifArgs.Style(); + + if (WI_IsFlagSet(style, OutputNotificationStyle::Taskbar)) + { + tab->TabRaiseVisualBell.raise(); + } + + if (WI_IsFlagSet(style, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::Audible)) + { + if (const auto termContent{ sender.try_as() }) + { + termContent.PlayNotificationSound(); + } + } + + if (WI_IsFlagSet(style, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::Tab)) + { + tab->ShowActivityIndicator(true); + + if (tab->_focusState != WUX::FocusState::Unfocused) + { + tab->ActivateActivityIndicatorTimer(); + } + } + + if (WI_IsFlagSet(style, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::Notification)) + { + const auto title = notifArgs.Title().empty() ? tab->Title() : notifArgs.Title(); + tab->TabToastNotificationRequested.raise(title, notifArgs.Body(), sender); + } } }); @@ -1414,6 +1482,10 @@ namespace winrt::TerminalApp::implementation { tab->ShowBellIndicator(false); } + if (tab->_tabStatus.ActivityIndicator()) + { + tab->ShowActivityIndicator(false); + } } }); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 1a9d43dd7b..b78ac0cebd 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -48,6 +48,9 @@ namespace winrt::TerminalApp::implementation void ShowBellIndicator(const bool show); void ActivateBellIndicatorTimer(); + void ShowActivityIndicator(const bool show); + void ActivateActivityIndicatorTimer(); + float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; std::optional PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, @@ -215,6 +218,9 @@ namespace winrt::TerminalApp::implementation SafeDispatcherTimer _bellIndicatorTimer; void _BellIndicatorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); + SafeDispatcherTimer _activityIndicatorTimer; + void _ActivityIndicatorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); + void _UpdateHeaderControlMaxWidth(); void _CreateContextMenu(); diff --git a/src/cascadia/TerminalApp/TabHeaderControl.xaml b/src/cascadia/TerminalApp/TabHeaderControl.xaml index d44d7a0c7d..83fc04f7b8 100644 --- a/src/cascadia/TerminalApp/TabHeaderControl.xaml +++ b/src/cascadia/TerminalApp/TabHeaderControl.xaml @@ -32,6 +32,12 @@ FontSize="12" Glyph="" Visibility="{x:Bind TabStatus.BellIndicator, Mode=OneWay}" /> + + void TerminalPaneContent::PlayNotificationSound() + { + if (_profile) + { + auto sounds{ _profile.BellSound() }; + if (sounds && sounds.Size() > 0) + { + winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() }; + winrt::Windows::Foundation::Uri uri{ soundPath }; + _playBellSound(uri); + } + else + { + const auto soundAlias = reinterpret_cast(SND_ALIAS_SYSTEMHAND); + PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); + } + } + } + void TerminalPaneContent::_controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) { @@ -272,18 +319,7 @@ namespace winrt::TerminalApp::implementation if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible)) { // Audible is set, play the sound - auto sounds{ _profile.BellSound() }; - if (sounds && sounds.Size() > 0) - { - winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() }; - winrt::Windows::Foundation::Uri uri{ soundPath }; - _playBellSound(uri); - } - else - { - const auto soundAlias = reinterpret_cast(SND_ALIAS_SYSTEMHAND); - PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY); - } + PlayNotificationSound(); } if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window)) @@ -298,6 +334,55 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPaneContent::_controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + { + if (_profile) + { + const auto notifyStyle = _profile.NotifyOnNextPrompt(); + if (static_cast(notifyStyle) != 0) + { + NotificationRequested.raise(*this, + *winrt::make_self(notifyStyle, true)); + } + + const auto autoDetect = _profile.AutoDetectRunningCommand(); + if (autoDetect != AutoDetectRunningCommand::Disabled && _autoDetectActive) + { + _autoDetectActive = false; + TaskbarProgressChanged.raise(*this, nullptr); + } + } + } + + void TerminalPaneContent::_controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + { + if (_profile) + { + const auto autoDetect = _profile.AutoDetectRunningCommand(); + if (autoDetect != AutoDetectRunningCommand::Disabled && !_autoDetectActive) + { + _autoDetectActive = true; + TaskbarProgressChanged.raise(*this, nullptr); + } + } + } + + void TerminalPaneContent::_controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + { + if (_profile) + { + const auto notifyStyle = _profile.NotifyOnInactiveOutput(); + if (static_cast(notifyStyle) != 0) + { + NotificationRequested.raise(*this, + *winrt::make_self(notifyStyle, true)); + } + } + } + safe_void_coroutine TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri) { auto weakThis{ get_weak() }; diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index b2f38ba249..e5c7db221d 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -23,9 +23,11 @@ namespace winrt::TerminalApp::implementation struct NotificationEventArgs : public NotificationEventArgsT { public: - NotificationEventArgs(const winrt::hstring& title = {}, const winrt::hstring& body = {}) : - Title(title), Body(body) {} + NotificationEventArgs(winrt::Microsoft::Terminal::Control::OutputNotificationStyle style, bool onlyWhenInactive = false, const winrt::hstring& title = {}, const winrt::hstring& body = {}) : + Style(style), OnlyWhenInactive(onlyWhenInactive), Title(title), Body(body) {} + til::property Style; + til::property OnlyWhenInactive; til::property Title; til::property Body; }; @@ -47,6 +49,7 @@ namespace winrt::TerminalApp::implementation void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); void MarkAsDefterm(); + void PlayNotificationSound(); winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const { @@ -54,8 +57,8 @@ namespace winrt::TerminalApp::implementation } winrt::hstring Title() { return _control.Title(); } - uint64_t TaskbarState() { return _control.TaskbarState(); } - uint64_t TaskbarProgress() { return _control.TaskbarProgress(); } + uint64_t TaskbarState(); + uint64_t TaskbarProgress(); bool ReadOnly() { return _control.ReadOnly(); } winrt::hstring Icon() const; Windows::Foundation::IReference TabColor() const noexcept; @@ -77,11 +80,15 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; bool _bellPlayerCreated{ false }; + std::atomic _autoDetectActive{ false }; struct ControlEventTokens { winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; + winrt::Microsoft::Terminal::Control::TermControl::PromptStarted_revoker _PromptStarted; + winrt::Microsoft::Terminal::Control::TermControl::OutputStarted_revoker _OutputStarted; + winrt::Microsoft::Terminal::Control::TermControl::OutputIdle_revoker _OutputIdle; winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; @@ -100,6 +107,9 @@ namespace winrt::TerminalApp::implementation safe_void_coroutine _controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); + void _controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); + void _controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); + void _controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); void _controlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); void _controlTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.idl b/src/cascadia/TerminalApp/TerminalPaneContent.idl index 53dcf4c337..36a1612c18 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.idl +++ b/src/cascadia/TerminalApp/TerminalPaneContent.idl @@ -11,6 +11,7 @@ namespace TerminalApp Microsoft.Terminal.Control.TermControl GetTermControl(); void MarkAsDefterm(); + void PlayNotificationSound(); Microsoft.Terminal.Settings.Model.Profile GetProfile(); diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.h b/src/cascadia/TerminalApp/TerminalTabStatus.h index 8d1d015193..81166b89ae 100644 --- a/src/cascadia/TerminalApp/TerminalTabStatus.h +++ b/src/cascadia/TerminalApp/TerminalTabStatus.h @@ -17,6 +17,7 @@ namespace winrt::TerminalApp::implementation WINRT_OBSERVABLE_PROPERTY(bool, IsProgressRingActive, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(bool, IsProgressRingIndeterminate, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(bool, BellIndicator, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(bool, ActivityIndicator, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(bool, IsReadOnlyActive, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(uint32_t, ProgressValue, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(bool, IsInputBroadcastActive, PropertyChanged.raise); diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.idl b/src/cascadia/TerminalApp/TerminalTabStatus.idl index d5664c76c6..f91199a632 100644 --- a/src/cascadia/TerminalApp/TerminalTabStatus.idl +++ b/src/cascadia/TerminalApp/TerminalTabStatus.idl @@ -12,6 +12,7 @@ namespace TerminalApp Boolean IsProgressRingActive { get; set; }; Boolean IsProgressRingIndeterminate { get; set; }; Boolean BellIndicator { get; set; }; + Boolean ActivityIndicator { get; set; }; UInt32 ProgressValue { get; set; }; Boolean IsReadOnlyActive { get; set; }; Boolean IsInputBroadcastActive { get; set; }; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 6f4ed75321..b4e3783113 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -139,6 +139,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnSearchMissingCommand = [this](auto&& PH1, auto&& PH2) { _terminalSearchMissingCommand(std::forward(PH1), std::forward(PH2)); }; _terminal->SetSearchMissingCommandCallback(pfnSearchMissingCommand); + auto pfnPromptStarted = [this] { _terminalPromptStarted(); }; + _terminal->SetPromptStartedCallback(pfnPromptStarted); + + auto pfnOutputStarted = [this] { _terminalOutputStarted(); }; + _terminal->SetOutputStartedCallback(pfnOutputStarted); + auto pfnClearQuickFix = [this] { ClearQuickFix(); }; _terminal->SetClearQuickFixCallback(pfnClearQuickFix); @@ -1596,11 +1602,33 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Since this can only ever be triggered by output from the connection, // then the Terminal already has the write lock when calling this // callback. + if (_restoring) + { + return; + } WarningBell.raise(*this, nullptr); } + void ControlCore::_terminalPromptStarted() + { + if (_restoring) + { + return; + } + PromptStarted.raise(*this, nullptr); + } + + void ControlCore::_terminalOutputStarted() + { + if (_restoring) + { + return; + } + OutputStarted.raise(*this, nullptr); + } + // Method Description: - // - Called for the Terminal's TitleChanged callback. This will re-raise + // - Called for the Terminal's TitleChanged callback.This will re-raise // a new winrt TypedEvent that can be listened to. // - The listeners to this event will re-query the control for the current // value of Title(). @@ -1656,6 +1684,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_terminalTaskbarProgressChanged() { + if (_restoring) + { + return; + } TaskbarProgressChanged.raise(*this, nullptr); } @@ -1673,6 +1705,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - duration - How long the note should be sustained (in microseconds). void ControlCore::_terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) { + if (_restoring) + { + return; + } // The UI thread might try to acquire the console lock from time to time. // --> Unlock it, so the UI doesn't hang while we're busy. const auto suspension = _terminal->SuspendLock(); @@ -1845,8 +1881,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->SerializeMainBuffer(handle); } - void ControlCore::RestoreFromPath(const wchar_t* path) const + void ControlCore::RestoreFromPath(const wchar_t* path) { + _restoring = true; + const auto restoreComplete = wil::scope_exit([&] { _restoring = false; }); wil::unique_handle file{ CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr) }; // This block of code exists temporarily to fix buffer dumps that were diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index e66d778f61..686f07b358 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -154,7 +154,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void Close(); void PersistTo(HANDLE handle) const; - void RestoreFromPath(const wchar_t* path) const; + void RestoreFromPath(const wchar_t* path); void ClearQuickFix(); @@ -275,6 +275,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event TitleChanged; til::typed_event WriteToClipboard; til::typed_event<> WarningBell; + til::typed_event<> PromptStarted; + til::typed_event<> OutputStarted; til::typed_event<> TabColorChanged; til::typed_event<> BackgroundColorChanged; til::typed_event ScrollPositionChanged; @@ -323,6 +325,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation #pragma region TerminalCoreCallbacks void _terminalWarningBell(); + void _terminalPromptStarted(); + void _terminalOutputStarted(); void _terminalTitleChanged(std::wstring_view wstr); void _terminalScrollPositionChanged(const int viewTop, const int viewHeight, @@ -419,6 +423,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::optional _lastHoveredCell; uint16_t _lastHoveredId{ 0 }; std::atomic _initializedTerminal{ false }; + std::atomic _restoring{ false }; bool _isReadOnly{ false }; bool _closing{ false }; diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 31bc24d593..725090b893 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -192,6 +192,8 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler WriteToClipboard; event Windows.Foundation.TypedEventHandler WarningBell; + event Windows.Foundation.TypedEventHandler PromptStarted; + event Windows.Foundation.TypedEventHandler OutputStarted; event Windows.Foundation.TypedEventHandler TabColorChanged; event Windows.Foundation.TypedEventHandler BackgroundColorChanged; event Windows.Foundation.TypedEventHandler TaskbarProgressChanged; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 4682668e92..8139a06db9 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -29,6 +29,23 @@ namespace Microsoft.Terminal.Control MinGW, }; + [flags] + enum OutputNotificationStyle + { + Taskbar = 0x1, + Audible = 0x2, + Tab = 0x4, + Notification = 0x8, + All = 0xffffffff + }; + + enum AutoDetectRunningCommand + { + Disabled, + Automatic, + Progress + }; + // Class Description: // TerminalSettings encapsulates all settings that control the // TermControl's behavior. In these settings there is both the entirety @@ -78,6 +95,10 @@ namespace Microsoft.Terminal.Control PathTranslationStyle PathTranslationStyle { get; }; String DragDropDelimiter { get; }; + OutputNotificationStyle NotifyOnInactiveOutput { get; }; + OutputNotificationStyle NotifyOnNextPrompt { get; }; + AutoDetectRunningCommand AutoDetectRunningCommand { get; }; + // NOTE! When adding something here, make sure to update ControlProperties.h too! }; } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index a616bec79a..d550106d74 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -393,6 +393,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // attached content before we set up the throttled func, and that'll A/V _revokers.coreScrollPositionChanged = _core.ScrollPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_ScrollPositionChanged }); _revokers.WarningBell = _core.WarningBell(winrt::auto_revoke, { get_weak(), &TermControl::_coreWarningBell }); + _revokers.PromptStarted = _core.PromptStarted(winrt::auto_revoke, { get_weak(), &TermControl::_corePromptStarted }); + _revokers.OutputStarted = _core.OutputStarted(winrt::auto_revoke, { get_weak(), &TermControl::_coreOutputStarted }); static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); _autoScrollTimer.Interval(AutoScrollUpdateInterval); @@ -3726,6 +3728,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation _playWarningBell->Run(); } + void TermControl::_corePromptStarted(const IInspectable& /*sender*/, const IInspectable& /*args*/) + { + PromptStarted.raise(*this, nullptr); + } + + void TermControl::_coreOutputStarted(const IInspectable& /*sender*/, const IInspectable& /*args*/) + { + OutputStarted.raise(*this, nullptr); + } + hstring TermControl::ReadEntireBuffer() const { return _core.ReadEntireBuffer(); @@ -3843,6 +3855,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void TermControl::_coreOutputIdle(const IInspectable& /*sender*/, const IInspectable& /*args*/) { _refreshSearch(); + OutputIdle.raise(*this, nullptr); } void TermControl::OwningHwnd(uint64_t owner) diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 767596e95a..9080485758 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -211,6 +211,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event FocusFollowMouseRequested; til::typed_event Initialized; til::typed_event<> WarningBell; + til::typed_event<> PromptStarted; + til::typed_event<> OutputStarted; + til::typed_event<> OutputIdle; til::typed_event KeySent; til::typed_event CharSent; til::typed_event StringSent; @@ -424,6 +427,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args); void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); + void _corePromptStarted(const IInspectable& sender, const IInspectable& args); + void _coreOutputStarted(const IInspectable& sender, const IInspectable& args); void _coreOutputIdle(const IInspectable& sender, const IInspectable& args); winrt::Windows::Foundation::Point _toPosInDips(const Core::Point terminalCellPos); @@ -449,6 +454,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { Control::ControlCore::ScrollPositionChanged_revoker coreScrollPositionChanged; Control::ControlCore::WarningBell_revoker WarningBell; + Control::ControlCore::PromptStarted_revoker PromptStarted; + Control::ControlCore::OutputStarted_revoker OutputStarted; Control::ControlCore::RendererEnteredErrorState_revoker RendererEnteredErrorState; Control::ControlCore::BackgroundColorChanged_revoker BackgroundColorChanged; Control::ControlCore::FontSizeChanged_revoker FontSizeChanged; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 54ded1a0fd..9d3a93d7e9 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -69,6 +69,9 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler RaiseNotice; event Windows.Foundation.TypedEventHandler WarningBell; + event Windows.Foundation.TypedEventHandler PromptStarted; + event Windows.Foundation.TypedEventHandler OutputStarted; + event Windows.Foundation.TypedEventHandler OutputIdle; event Windows.Foundation.TypedEventHandler HidePointerCursor; event Windows.Foundation.TypedEventHandler RestorePointerCursor; event Windows.Foundation.TypedEventHandler TabColorChanged; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 39a0406278..ed3541e8b7 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -763,6 +763,11 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s // This changed the scrollbar marks - raise a notification to update them _NotifyScrollEvent(); } + // regardless, notify that we started command output + if (_pfnOutputStarted) + { + _pfnOutputStarted(); + } } } @@ -1277,6 +1282,16 @@ void Microsoft::Terminal::Core::Terminal::SetClearQuickFixCallback(std::function _pfnClearQuickFix.swap(pfn); } +void Terminal::SetPromptStartedCallback(std::function pfn) noexcept +{ + _pfnPromptStarted.swap(pfn); +} + +void Terminal::SetOutputStartedCallback(std::function pfn) noexcept +{ + _pfnOutputStarted.swap(pfn); +} + // Method Description: // - Stores the search highlighted regions in the terminal void Terminal::SetSearchHighlights(const std::vector& highlights) noexcept diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 6c32070e2b..52a449383c 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -159,7 +159,7 @@ public: bool IsVtInputEnabled() const noexcept override; void NotifyBufferRotation(const int delta) override; - void NotifyShellIntegrationMark() override; + void NotifyShellIntegrationMark(ShellIntegrationMark mark) override; void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override; @@ -235,6 +235,8 @@ public: void SetSearchMissingCommandCallback(std::function pfn) noexcept; void SetClearQuickFixCallback(std::function pfn) noexcept; void SetWindowSizeChangedCallback(std::function pfn) noexcept; + void SetPromptStartedCallback(std::function pfn) noexcept; + void SetOutputStartedCallback(std::function pfn) noexcept; void SetSearchHighlights(const std::vector& highlights) noexcept; void SetSearchHighlightFocused(size_t focusedIdx) noexcept; void ScrollToSearchHighlight(til::CoordType searchScrollOffset); @@ -343,6 +345,8 @@ private: std::function _pfnSearchMissingCommand; std::function _pfnClearQuickFix; std::function _pfnWindowSizeChanged; + std::function _pfnPromptStarted; + std::function _pfnOutputStarted; RenderSettings _renderSettings; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index bf7ed1ea8d..1b67e45dcb 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -405,8 +405,26 @@ void Terminal::NotifyBufferRotation(const int delta) } } -void Terminal::NotifyShellIntegrationMark() +void Terminal::NotifyShellIntegrationMark(ShellIntegrationMark mark) { // Notify the scrollbar that marks have been added so it can refresh the mark indicators _NotifyScrollEvent(); + + switch (mark) + { + case ShellIntegrationMark::Prompt: + if (_pfnPromptStarted) + { + _pfnPromptStarted(); + } + break; + case ShellIntegrationMark::Output: + if (_pfnOutputStarted) + { + _pfnOutputStarted(); + } + break; + default: + break; + } } diff --git a/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp b/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp index e9106f6878..e14c0dca45 100644 --- a/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp @@ -354,6 +354,9 @@ namespace winrt::Microsoft::Terminal::Settings _AllowVtClipboardWrite = profile.AllowVtClipboardWrite(); _PathTranslationStyle = profile.PathTranslationStyle(); _DragDropDelimiter = profile.DragDropDelimiter(); + _NotifyOnInactiveOutput = profile.NotifyOnInactiveOutput(); + _NotifyOnNextPrompt = profile.NotifyOnNextPrompt(); + _AutoDetectRunningCommand = profile.AutoDetectRunningCommand(); } // Method Description: diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 661d077b7e..753372cd89 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -40,6 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle, L"Profile_PathTranslationStyle", L"Content"); + INITIALIZE_BINDABLE_ENUM_SETTING(AutoDetectRunningCommand, AutoDetectRunningCommand, winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand, L"Profile_AutoDetectRunningCommand", L"Content"); _InitializeCurrentBellSounds(); @@ -106,6 +107,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _NotifyChanges(L"CurrentPathTranslationStyle"); } + else if (viewModelProperty == L"NotifyOnInactiveOutput") + { + _NotifyChanges(L"IsNotifyOnInactiveOutputFlagSet", L"NotifyOnInactiveOutputPreview"); + } + else if (viewModelProperty == L"NotifyOnNextPrompt") + { + _NotifyChanges(L"IsNotifyOnNextPromptFlagSet", L"NotifyOnNextPromptPreview"); + } + else if (viewModelProperty == L"AutoDetectRunningCommand") + { + _NotifyChanges(L"CurrentAutoDetectRunningCommand"); + } else if (viewModelProperty == L"Padding") { _parsedPadding = StringToXamlThickness(_profile.Padding()); @@ -640,6 +653,170 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation BellStyle(currentStyle); } + // ===================== NotifyOnInactiveOutput ===================== + + hstring ProfileViewModel::NotifyOnInactiveOutputPreview() const + { + using Ons = Control::OutputNotificationStyle; + const auto style = NotifyOnInactiveOutput(); + if (WI_AreAllFlagsSet(style, Ons::Taskbar | Ons::Audible | Ons::Tab | Ons::Notification)) + { + return RS_(L"Profile_OutputNotificationStyleAll/Content"); + } + else if (style == static_cast(0)) + { + return RS_(L"Profile_OutputNotificationStyleNone/Content"); + } + + std::vector resultList; + resultList.reserve(4); + if (WI_IsFlagSet(style, Ons::Taskbar)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTaskbar/Content")); + } + if (WI_IsFlagSet(style, Ons::Audible)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleAudible/Content")); + } + if (WI_IsFlagSet(style, Ons::Tab)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTab/Content")); + } + if (WI_IsFlagSet(style, Ons::Notification)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleNotification/Content")); + } + + hstring result{}; + for (auto&& entry : resultList) + { + if (result.empty()) + { + result = entry; + } + else + { + result = result + L", " + entry; + } + } + return result; + } + + bool ProfileViewModel::IsNotifyOnInactiveOutputFlagSet(const uint32_t flag) + { + return (WI_EnumValue(NotifyOnInactiveOutput()) & flag) == flag; + } + + void ProfileViewModel::SetNotifyOnInactiveOutputTaskbar(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnInactiveOutput(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Taskbar, winrt::unbox_value(on)); + NotifyOnInactiveOutput(currentStyle); + } + + void ProfileViewModel::SetNotifyOnInactiveOutputAudible(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnInactiveOutput(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Audible, winrt::unbox_value(on)); + NotifyOnInactiveOutput(currentStyle); + } + + void ProfileViewModel::SetNotifyOnInactiveOutputTab(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnInactiveOutput(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Tab, winrt::unbox_value(on)); + NotifyOnInactiveOutput(currentStyle); + } + + void ProfileViewModel::SetNotifyOnInactiveOutputNotification(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnInactiveOutput(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Notification, winrt::unbox_value(on)); + NotifyOnInactiveOutput(currentStyle); + } + + // ===================== NotifyOnNextPrompt ===================== + + hstring ProfileViewModel::NotifyOnNextPromptPreview() const + { + using Ons = Control::OutputNotificationStyle; + const auto style = NotifyOnNextPrompt(); + if (WI_AreAllFlagsSet(style, Ons::Taskbar | Ons::Audible | Ons::Tab | Ons::Notification)) + { + return RS_(L"Profile_OutputNotificationStyleAll/Content"); + } + else if (style == static_cast(0)) + { + return RS_(L"Profile_OutputNotificationStyleNone/Content"); + } + + std::vector resultList; + resultList.reserve(4); + if (WI_IsFlagSet(style, Ons::Taskbar)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTaskbar/Content")); + } + if (WI_IsFlagSet(style, Ons::Audible)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleAudible/Content")); + } + if (WI_IsFlagSet(style, Ons::Tab)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTab/Content")); + } + if (WI_IsFlagSet(style, Ons::Notification)) + { + resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleNotification/Content")); + } + + hstring result{}; + for (auto&& entry : resultList) + { + if (result.empty()) + { + result = entry; + } + else + { + result = result + L", " + entry; + } + } + return result; + } + + bool ProfileViewModel::IsNotifyOnNextPromptFlagSet(const uint32_t flag) + { + return (WI_EnumValue(NotifyOnNextPrompt()) & flag) == flag; + } + + void ProfileViewModel::SetNotifyOnNextPromptTaskbar(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnNextPrompt(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Taskbar, winrt::unbox_value(on)); + NotifyOnNextPrompt(currentStyle); + } + + void ProfileViewModel::SetNotifyOnNextPromptAudible(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnNextPrompt(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Audible, winrt::unbox_value(on)); + NotifyOnNextPrompt(currentStyle); + } + + void ProfileViewModel::SetNotifyOnNextPromptTab(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnNextPrompt(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Tab, winrt::unbox_value(on)); + NotifyOnNextPrompt(currentStyle); + } + + void ProfileViewModel::SetNotifyOnNextPromptNotification(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = NotifyOnNextPrompt(); + WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Notification, winrt::unbox_value(on)); + NotifyOnNextPrompt(currentStyle); + } + // Method Description: // - Construct _CurrentBellSounds by importing the _inherited_ value from the model // - Adds a PropertyChanged handler to each BellSoundViewModel to propagate changes to the model diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 1112a2f8ef..d7027b1f32 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -47,6 +47,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void SetBellStyleWindow(winrt::Windows::Foundation::IReference on); void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on); + // notify on inactive output bits + hstring NotifyOnInactiveOutputPreview() const; + bool IsNotifyOnInactiveOutputFlagSet(const uint32_t flag); + void SetNotifyOnInactiveOutputTaskbar(winrt::Windows::Foundation::IReference on); + void SetNotifyOnInactiveOutputAudible(winrt::Windows::Foundation::IReference on); + void SetNotifyOnInactiveOutputTab(winrt::Windows::Foundation::IReference on); + void SetNotifyOnInactiveOutputNotification(winrt::Windows::Foundation::IReference on); + + // notify on next prompt bits + hstring NotifyOnNextPromptPreview() const; + bool IsNotifyOnNextPromptFlagSet(const uint32_t flag); + void SetNotifyOnNextPromptTaskbar(winrt::Windows::Foundation::IReference on); + void SetNotifyOnNextPromptAudible(winrt::Windows::Foundation::IReference on); + void SetNotifyOnNextPromptTab(winrt::Windows::Foundation::IReference on); + void SetNotifyOnNextPromptNotification(winrt::Windows::Foundation::IReference on); + hstring BellSoundPreview(); void RequestAddBellSound(hstring path); void RequestDeleteBellSound(const Editor::BellSoundViewModel& vm); @@ -146,6 +162,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile, RainbowSuggestions); OBSERVABLE_PROJECTED_SETTING(_profile, PathTranslationStyle); OBSERVABLE_PROJECTED_SETTING(_profile, DragDropDelimiter); + OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnInactiveOutput); + OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnNextPrompt); + OBSERVABLE_PROJECTED_SETTING(_profile, AutoDetectRunningCommand); WINRT_PROPERTY(bool, IsBaseLayer, false); WINRT_PROPERTY(bool, FocusDeleteButton, false); @@ -153,6 +172,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation GETSET_BINDABLE_ENUM_SETTING(CloseOnExitMode, Microsoft::Terminal::Settings::Model::CloseOnExitMode, CloseOnExit); GETSET_BINDABLE_ENUM_SETTING(ScrollState, Microsoft::Terminal::Control::ScrollbarState, ScrollState); GETSET_BINDABLE_ENUM_SETTING(PathTranslationStyle, Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle); + GETSET_BINDABLE_ENUM_SETTING(AutoDetectRunningCommand, Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand); private: Model::Profile _profile; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index 8b1aeba792..6efd5caa60 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -50,6 +50,23 @@ namespace Microsoft.Terminal.Settings.Editor void SetBellStyleWindow(Windows.Foundation.IReference on); void SetBellStyleTaskbar(Windows.Foundation.IReference on); + String NotifyOnInactiveOutputPreview { get; }; + Boolean IsNotifyOnInactiveOutputFlagSet(UInt32 flag); + void SetNotifyOnInactiveOutputTaskbar(Windows.Foundation.IReference on); + void SetNotifyOnInactiveOutputAudible(Windows.Foundation.IReference on); + void SetNotifyOnInactiveOutputTab(Windows.Foundation.IReference on); + void SetNotifyOnInactiveOutputNotification(Windows.Foundation.IReference on); + + String NotifyOnNextPromptPreview { get; }; + Boolean IsNotifyOnNextPromptFlagSet(UInt32 flag); + void SetNotifyOnNextPromptTaskbar(Windows.Foundation.IReference on); + void SetNotifyOnNextPromptAudible(Windows.Foundation.IReference on); + void SetNotifyOnNextPromptTab(Windows.Foundation.IReference on); + void SetNotifyOnNextPromptNotification(Windows.Foundation.IReference on); + + IInspectable CurrentAutoDetectRunningCommand; + Windows.Foundation.Collections.IObservableVector AutoDetectRunningCommandList { get; }; + String BellSoundPreview { get; }; Windows.Foundation.Collections.IObservableVector CurrentBellSounds { get; }; void RequestAddBellSound(String path); @@ -141,5 +158,8 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle); OBSERVABLE_PROJECTED_PROFILE_SETTING(String, DragDropDelimiter); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtClipboardWrite); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnInactiveOutput); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand); } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 941f410e97..8c0d1313e5 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -196,6 +196,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Flash window An option to choose from for the "bell style" setting. When selected, a visual notification is used to notify the user. In this case, the window is flashed. + + None + An option to choose from for the notification style setting. When selected, no notification is sent. + + + All + An option to choose from for the notification style setting. When selected, all notification methods are used. + + + Flash taskbar + An option to choose from for the notification style setting. Flashes the taskbar icon. + + + Play sound + An option to choose from for the notification style setting. Plays an audible notification sound. + + + Show tab indicator + An option to choose from for the notification style setting. Shows an activity indicator on the tab. + + + Desktop notification + An option to choose from for the notification style setting. Sends a Windows desktop notification (toast). + + + Notify on inactive tab output + Header for a control to set the notification style when an inactive tab produces output. + + + Choose how to be notified when a background tab produces new output. + Help text for the notify on inactive output setting. + + + Notify on next prompt + Header for a control to set the notification style when a new shell prompt is detected (requires shell integration). + + + Choose how to be notified when a command finishes and a new prompt appears. Requires shell integration. + Help text for the notify on next prompt setting. + + + Auto-detect running command + Header for a control to configure automatic detection of a running command's progress state. + + + Automatically show a progress indicator when a command is running. Requires shell integration. + Help text for the auto-detect running command setting. + + + Disabled + An option for the auto-detect running command setting. When selected, automatic command detection is off. + + + Automatic + An option for the auto-detect running command setting. When selected, the terminal automatically detects running commands and shows a progress indicator. + + + Progress + An option for the auto-detect running command setting. When selected, the terminal shows command progress by walking the buffer (not yet implemented). + Add new Button label that creates a new color scheme. diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index de1bf5185d..d4e45adb96 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -53,6 +53,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::IntenseStyle, IntenseTextStyle); DEFINE_ENUM_MAP(Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors); DEFINE_ENUM_MAP(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle); + DEFINE_ENUM_MAP(Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand); // Actions DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::ResizeDirection, ResizeDirection); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index 160c9a11b1..545ac6adca 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -51,6 +51,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap IntenseTextStyle(); static winrt::Windows::Foundation::Collections::IMap AdjustIndistinguishableColors(); static winrt::Windows::Foundation::Collections::IMap PathTranslationStyle(); + static winrt::Windows::Foundation::Collections::IMap AutoDetectRunningCommand(); // Actions static winrt::Windows::Foundation::Collections::IMap ResizeDirection(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index 128260a507..4749ab7c77 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -33,6 +33,7 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap FontWeight { get; }; static Windows.Foundation.Collections.IMap IntenseTextStyle { get; }; static Windows.Foundation.Collections.IMap PathTranslationStyle { get; }; + static Windows.Foundation.Collections.IMap AutoDetectRunningCommand { get; }; // Actions static Windows.Foundation.Collections.IMap ResizeDirection { get; }; diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index b95aad938e..d24760a371 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -79,37 +79,40 @@ Author(s): // * TerminalSettings.cpp: TerminalSettings::_ApplyProfileSettings // * IControlSettings.idl or ICoreSettings.idl // * ControlProperties.h -#define MTSM_PROFILE_SETTINGS(X) \ - X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ - X(bool, SnapOnInput, "snapOnInput", true) \ - X(bool, AltGrAliasing, "altGrAliasing", true) \ - X(hstring, AnswerbackMessage, "answerbackMessage") \ - X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ - X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ - X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(hstring, StartingDirectory, "startingDirectory") \ - X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ - X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ - X(guid, ConnectionType, "connectionType") \ - X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ - X(hstring, TabTitle, "tabTitle") \ - X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ - X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ - X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ - X(bool, Elevate, "elevate", false) \ - X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ - X(bool, ShowMarks, "showMarksOnScrollbar", false) \ - X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ - X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ - X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ - X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ - X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ - X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ - X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ - X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ - X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ - X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) +#define MTSM_PROFILE_SETTINGS(X) \ + X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ + X(bool, SnapOnInput, "snapOnInput", true) \ + X(bool, AltGrAliasing, "altGrAliasing", true) \ + X(hstring, AnswerbackMessage, "answerbackMessage") \ + X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ + X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ + X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ + X(hstring, StartingDirectory, "startingDirectory") \ + X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ + X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ + X(guid, ConnectionType, "connectionType") \ + X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ + X(hstring, TabTitle, "tabTitle") \ + X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ + X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ + X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ + X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ + X(bool, Elevate, "elevate", false) \ + X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ + X(bool, ShowMarks, "showMarksOnScrollbar", false) \ + X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ + X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ + X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ + X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ + X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ + X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ + X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ + X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ + X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ + X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ + X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnInactiveOutput, "notifyOnInactiveOutput", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ + X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ + X(Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand, "autoDetectRunningCommand", Microsoft::Terminal::Control::AutoDetectRunningCommand::Disabled) // Intentionally omitted Profile settings: // * Name diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 96bc79ad19..4acfee419d 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -95,5 +95,8 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle); INHERITABLE_PROFILE_SETTING(String, DragDropDelimiter); + INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnInactiveOutput); + INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); + INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand); } } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 9c071945a2..d7d291f2ac 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -119,6 +119,46 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::BellStyle) } }; +JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Control::OutputNotificationStyle) +{ + static constexpr std::array mappings = { + pair_type{ "none", AllClear }, + pair_type{ "taskbar", ValueType::Taskbar }, + pair_type{ "audible", ValueType::Audible }, + pair_type{ "tab", ValueType::Tab }, + pair_type{ "notification", ValueType::Notification }, + pair_type{ "all", AllSet }, + }; + + auto FromJson(const Json::Value& json) + { + if (json.isBool()) + { + return json.asBool() ? ValueType::Tab : AllClear; + } + return BaseFlagMapper::FromJson(json); + } + + bool CanConvert(const Json::Value& json) + { + return BaseFlagMapper::CanConvert(json) || json.isBool(); + } + + Json::Value ToJson(const ::winrt::Microsoft::Terminal::Control::OutputNotificationStyle& style) + { + return BaseFlagMapper::ToJson(style); + } +}; + +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand) +{ + JSON_MAPPINGS(3) = { + pair_type{ "disabled", ValueType::Disabled }, + pair_type{ "automatic", ValueType::Automatic }, + pair_type{ "progress", ValueType::Progress }, + }; +}; + JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::ConvergedAlignment) { // reduce repetition diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index cb4750d5e6..02ff6d5abc 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -60,32 +60,35 @@ // --------------------------- Control Settings --------------------------- // All of these settings are defined in IControlSettings. -#define CONTROL_SETTINGS(X) \ - X(winrt::guid, SessionId) \ - X(bool, EnableUnfocusedAcrylic, false) \ - X(winrt::hstring, Padding, DEFAULT_PADDING) \ - X(winrt::hstring, FontFace, L"Consolas") \ - X(float, FontSize, DEFAULT_FONT_SIZE) \ - X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ - X(IFontFeatureMap, FontFeatures) \ - X(IFontAxesMap, FontAxes) \ - X(bool, EnableBuiltinGlyphs, true) \ - X(bool, EnableColorGlyphs, true) \ - X(winrt::hstring, CellWidth) \ - X(winrt::hstring, CellHeight) \ - X(winrt::hstring, Commandline) \ - X(winrt::hstring, StartingDirectory) \ - X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible) \ - X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI) \ - X(bool, DisablePartialInvalidation, false) \ - X(bool, SoftwareRendering, false) \ - X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement) \ - X(winrt::Microsoft::Terminal::Control::AmbiguousWidth, AmbiguousWidth, winrt::Microsoft::Terminal::Control::AmbiguousWidth::Narrow) \ - X(winrt::Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope::Default) \ - X(bool, UseBackgroundImageForWindow, false) \ - X(bool, ShowMarks, false) \ - X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ - X(bool, RightClickContextMenu, false) \ - X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ +#define CONTROL_SETTINGS(X) \ + X(winrt::guid, SessionId) \ + X(bool, EnableUnfocusedAcrylic, false) \ + X(winrt::hstring, Padding, DEFAULT_PADDING) \ + X(winrt::hstring, FontFace, L"Consolas") \ + X(float, FontSize, DEFAULT_FONT_SIZE) \ + X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ + X(IFontFeatureMap, FontFeatures) \ + X(IFontAxesMap, FontAxes) \ + X(bool, EnableBuiltinGlyphs, true) \ + X(bool, EnableColorGlyphs, true) \ + X(winrt::hstring, CellWidth) \ + X(winrt::hstring, CellHeight) \ + X(winrt::hstring, Commandline) \ + X(winrt::hstring, StartingDirectory) \ + X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible) \ + X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ + X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI) \ + X(bool, DisablePartialInvalidation, false) \ + X(bool, SoftwareRendering, false) \ + X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement) \ + X(winrt::Microsoft::Terminal::Control::AmbiguousWidth, AmbiguousWidth, winrt::Microsoft::Terminal::Control::AmbiguousWidth::Narrow) \ + X(winrt::Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope::Default) \ + X(bool, UseBackgroundImageForWindow, false) \ + X(bool, ShowMarks, false) \ + X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ + X(bool, RightClickContextMenu, false) \ + X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ + X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnInactiveOutput, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ + X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ + X(winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand, winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand::Disabled) \ X(winrt::hstring, DragDropDelimiter, L" ") diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index df07c620d6..875806447f 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -446,7 +446,7 @@ void ConhostInternalGetSet::NotifyBufferRotation(const int) { } -void ConhostInternalGetSet::NotifyShellIntegrationMark() +void ConhostInternalGetSet::NotifyShellIntegrationMark(ShellIntegrationMark /*mark*/) { // Not implemented for conhost - shell integration marks are a Terminal app feature. } diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 2591e43048..4ae4d21e0f 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -68,7 +68,7 @@ public: bool IsVtInputEnabled() const override; void NotifyBufferRotation(const int delta) override; - void NotifyShellIntegrationMark() override; + void NotifyShellIntegrationMark(ShellIntegrationMark mark) override; void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override; diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index cb67bd1f60..9df8acf52c 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -87,7 +87,16 @@ namespace Microsoft::Console::VirtualTerminal virtual bool ResizeWindow(const til::CoordType width, const til::CoordType height) = 0; virtual void NotifyBufferRotation(const int delta) = 0; - virtual void NotifyShellIntegrationMark() = 0; + + enum class ShellIntegrationMark + { + Prompt, + Command, + Output, + CommandFinished, + Other + }; + virtual void NotifyShellIntegrationMark(ShellIntegrationMark mark) = 0; virtual void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) = 0; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index e1fdb6a69c..0646ff45f2 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3640,7 +3640,7 @@ void AdaptDispatch::DoConEmuAction(const std::wstring_view string) else if (subParam == 12) { _pages.ActivePage().Buffer().StartCommand(); - _api.NotifyShellIntegrationMark(); + _api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Command); } else { @@ -3675,7 +3675,7 @@ void AdaptDispatch::DoITerm2Action(const std::wstring_view string) if (action == L"SetMark") { _pages.ActivePage().Buffer().StartPrompt(); - _api.NotifyShellIntegrationMark(); + _api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Prompt); } else { @@ -3713,19 +3713,19 @@ void AdaptDispatch::DoFinalTermAction(const std::wstring_view string) case L'A': // FTCS_PROMPT { _pages.ActivePage().Buffer().StartPrompt(); - _api.NotifyShellIntegrationMark(); + _api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Prompt); break; } case L'B': // FTCS_COMMAND_START { _pages.ActivePage().Buffer().StartCommand(); - _api.NotifyShellIntegrationMark(); + _api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Command); break; } case L'C': // FTCS_COMMAND_EXECUTED { _pages.ActivePage().Buffer().StartOutput(); - _api.NotifyShellIntegrationMark(); + _api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Output); break; } case L'D': // FTCS_COMMAND_FINISHED @@ -3746,7 +3746,7 @@ void AdaptDispatch::DoFinalTermAction(const std::wstring_view string) } _pages.ActivePage().Buffer().EndCurrentCommand(error); - _api.NotifyShellIntegrationMark(); + _api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::CommandFinished); break; } From 62727bf6d152ca7ba5177045abd32f23476e6a5f Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 26 Mar 2026 19:27:24 -0700 Subject: [PATCH 09/24] minor cleanup --- src/cascadia/TerminalApp/TabManagement.cpp | 4 ++-- src/cascadia/TerminalControl/ControlCore.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 9c2621f712..dae4a165de 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -1281,8 +1281,8 @@ namespace winrt::TerminalApp::implementation { // The toast Activated callback runs on a background thread. // Marshal to the UI thread for tab focus and window summon. - page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakTab, weakContent]() { - if (const auto p{ weakPage.get() }) + page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakThis, weakTab]() { + if (const auto p{ weakThis.get() }) { if (const auto t{ weakTab.get() }) { diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b4e3783113..f49655dd63 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1628,7 +1628,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // Method Description: - // - Called for the Terminal's TitleChanged callback.This will re-raise + // - Called for the Terminal's TitleChanged callback. This will re-raise // a new winrt TypedEvent that can be listened to. // - The listeners to this event will re-query the control for the current // value of Title(). From da00f8863b32fc94ecf7f584109325a868ea3610 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 7 Apr 2026 14:37:08 -0700 Subject: [PATCH 10/24] Fix settings UI --- src/cascadia/TerminalApp/TabManagement.cpp | 2 +- src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml | 2 +- .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index dae4a165de..b1050d9068 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -1281,7 +1281,7 @@ namespace winrt::TerminalApp::implementation { // The toast Activated callback runs on a background thread. // Marshal to the UI thread for tab focus and window summon. - page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakThis, weakTab]() { + page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakThis, weakTab, weakContent]() { if (const auto p{ weakThis.get() }) { if (const auto t{ weakTab.get() }) diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 8c0d1313e5..b5564fd036 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -242,7 +242,7 @@ ClearSettingValue="{x:Bind Profile.ClearAutoDetectRunningCommand}" HasSettingValue="{x:Bind Profile.HasAutoDetectRunningCommand, Mode=OneWay}" SettingOverrideSource="{x:Bind Profile.AutoDetectRunningCommandOverrideSource, Mode=OneWay}"> - diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index be183d6959..ba54aed5b5 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -1601,15 +1601,15 @@ Automatically show a progress indicator when a command is running. Requires shell integration. Help text for the auto-detect running command setting. - + Disabled An option for the auto-detect running command setting. When selected, automatic command detection is off. - + Automatic An option for the auto-detect running command setting. When selected, the terminal automatically detects running commands and shows a progress indicator. - + Progress An option for the auto-detect running command setting. When selected, the terminal shows command progress by walking the buffer (not yet implemented). From 6c2b796253a9eb25e9aeffdd5f37a6ec64c6690b Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 29 Apr 2026 17:23:14 -0700 Subject: [PATCH 11/24] Address Leonard's feedback --- src/cascadia/TerminalApp/IPaneContent.idl | 6 +- .../TerminalApp/MarkdownPaneContent.h | 2 +- src/cascadia/TerminalApp/ScratchpadContent.h | 2 +- .../TerminalApp/SettingsPaneContent.h | 2 +- .../TerminalApp/SnippetsPaneContent.h | 2 +- src/cascadia/TerminalApp/Tab.cpp | 5 +- src/cascadia/TerminalApp/TaskbarState.cpp | 17 +-- src/cascadia/TerminalApp/TaskbarState.h | 4 +- src/cascadia/TerminalApp/TaskbarState.idl | 4 +- .../TerminalApp/TerminalPaneContent.cpp | 46 +------- .../TerminalApp/TerminalPaneContent.h | 5 +- src/cascadia/TerminalControl/ControlCore.cpp | 32 +++++- src/cascadia/TerminalControl/ControlCore.h | 4 +- .../TerminalControl/IControlSettings.idl | 14 +++ src/cascadia/TerminalControl/ICoreState.idl | 4 +- src/cascadia/TerminalControl/TermControl.cpp | 2 +- src/cascadia/TerminalControl/TermControl.h | 2 +- src/cascadia/TerminalCore/Terminal.cpp | 2 +- src/cascadia/TerminalCore/Terminal.hpp | 8 +- src/cascadia/TerminalCore/TerminalApi.cpp | 2 +- .../ProfileViewModel.cpp | 102 +++++++----------- .../TerminalApiTest.cpp | 24 +++-- src/cascadia/WindowsTerminal/AppHost.cpp | 2 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 13 +-- src/cascadia/WindowsTerminal/IslandWindow.h | 2 +- 25 files changed, 146 insertions(+), 162 deletions(-) diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index 605e377cdf..98dbf9f56a 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -18,10 +18,10 @@ namespace TerminalApp runtimeclass NotificationEventArgs { - Microsoft.Terminal.Control.OutputNotificationStyle Style { get; }; - Boolean OnlyWhenInactive { get; }; String Title { get; }; String Body { get; }; + Microsoft.Terminal.Control.OutputNotificationStyle Style { get; }; + Boolean OnlyWhenInactive { get; }; }; interface IPaneContent @@ -32,7 +32,7 @@ namespace TerminalApp Windows.Foundation.Size MinimumSize { get; }; String Title { get; }; - UInt64 TaskbarState { get; }; + Microsoft.Terminal.Control.TaskbarState TaskbarState { get; }; UInt64 TaskbarProgress { get; }; Boolean ReadOnly { get; }; String Icon { get; }; diff --git a/src/cascadia/TerminalApp/MarkdownPaneContent.h b/src/cascadia/TerminalApp/MarkdownPaneContent.h index 5f91b982f1..8b31a9f430 100644 --- a/src/cascadia/TerminalApp/MarkdownPaneContent.h +++ b/src/cascadia/TerminalApp/MarkdownPaneContent.h @@ -33,7 +33,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const; winrt::hstring Title() { return _filePath; } - uint64_t TaskbarState() { return 0; } + winrt::Microsoft::Terminal::Control::TaskbarState TaskbarState() { return winrt::Microsoft::Terminal::Control::TaskbarState::Clear; } uint64_t TaskbarProgress() { return 0; } bool ReadOnly() { return false; } winrt::hstring Icon() const; diff --git a/src/cascadia/TerminalApp/ScratchpadContent.h b/src/cascadia/TerminalApp/ScratchpadContent.h index 30053f5aff..cd2abbdd3a 100644 --- a/src/cascadia/TerminalApp/ScratchpadContent.h +++ b/src/cascadia/TerminalApp/ScratchpadContent.h @@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const; winrt::hstring Title() { return L"Scratchpad"; } - uint64_t TaskbarState() { return 0; } + winrt::Microsoft::Terminal::Control::TaskbarState TaskbarState() { return winrt::Microsoft::Terminal::Control::TaskbarState::Clear; } uint64_t TaskbarProgress() { return 0; } bool ReadOnly() { return false; } winrt::hstring Icon() const; diff --git a/src/cascadia/TerminalApp/SettingsPaneContent.h b/src/cascadia/TerminalApp/SettingsPaneContent.h index a119039cf1..7eb5253cb2 100644 --- a/src/cascadia/TerminalApp/SettingsPaneContent.h +++ b/src/cascadia/TerminalApp/SettingsPaneContent.h @@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(const BuildStartupKind kind) const; winrt::hstring Title() { return RS_(L"SettingsTab"); } - uint64_t TaskbarState() { return 0; } + winrt::Microsoft::Terminal::Control::TaskbarState TaskbarState() { return winrt::Microsoft::Terminal::Control::TaskbarState::Clear; } uint64_t TaskbarProgress() { return 0; } bool ReadOnly() { return false; } winrt::hstring Icon() const; diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.h b/src/cascadia/TerminalApp/SnippetsPaneContent.h index 1e25fb0cd2..2c641b498a 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.h +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.h @@ -24,7 +24,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const; winrt::hstring Title() { return RS_(L"SnippetPaneTitle/Text"); } - uint64_t TaskbarState() { return 0; } + winrt::Microsoft::Terminal::Control::TaskbarState TaskbarState() { return winrt::Microsoft::Terminal::Control::TaskbarState::Clear; } uint64_t TaskbarProgress() { return 0; } bool ReadOnly() { return false; } winrt::hstring Icon() const; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 2c5ae5e9cc..6c9983821c 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1302,11 +1302,10 @@ namespace winrt::TerminalApp::implementation const auto taskbarState = state.State(); // The progress of the control changed, but not necessarily the progress of the tab. // Set the tab's progress ring to the active pane's progress - if (taskbarState > 0) + if (taskbarState != winrt::Microsoft::Terminal::Control::TaskbarState::Clear) { - if (taskbarState == 3) + if (taskbarState == winrt::Microsoft::Terminal::Control::TaskbarState::Indeterminate) { - // 3 is the indeterminate state, set the progress ring as such _tabStatus.IsProgressRingIndeterminate(true); } else diff --git a/src/cascadia/TerminalApp/TaskbarState.cpp b/src/cascadia/TerminalApp/TaskbarState.cpp index ef9c34a75d..e996fcba4d 100644 --- a/src/cascadia/TerminalApp/TaskbarState.cpp +++ b/src/cascadia/TerminalApp/TaskbarState.cpp @@ -9,27 +9,28 @@ namespace winrt::TerminalApp::implementation { // Default to unset, 0%. TaskbarState::TaskbarState() : - TaskbarState(0, 0) {}; + TaskbarState(winrt::Microsoft::Terminal::Control::TaskbarState::Clear, 0) {}; - TaskbarState::TaskbarState(const uint64_t dispatchTypesState, const uint64_t progressParam) : - _State{ dispatchTypesState }, + TaskbarState::TaskbarState(const winrt::Microsoft::Terminal::Control::TaskbarState state, const uint64_t progressParam) : + _State{ state }, _Progress{ progressParam } {} uint64_t TaskbarState::Priority() const { // This seemingly nonsensical ordering is from // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group + using TbState = winrt::Microsoft::Terminal::Control::TaskbarState; switch (_State) { - case 0: // Clear = 0, + case TbState::Clear: return 5; - case 1: // Set = 1, + case TbState::Set: return 3; - case 2: // Error = 2, + case TbState::Error: return 1; - case 3: // Indeterminate = 3, + case TbState::Indeterminate: return 4; - case 4: // Paused = 4 + case TbState::Paused: return 2; } // Here, return 6, to definitely be greater than all the other valid values. diff --git a/src/cascadia/TerminalApp/TaskbarState.h b/src/cascadia/TerminalApp/TaskbarState.h index 36100eeec7..84b22793e8 100644 --- a/src/cascadia/TerminalApp/TaskbarState.h +++ b/src/cascadia/TerminalApp/TaskbarState.h @@ -16,13 +16,13 @@ namespace winrt::TerminalApp::implementation { public: TaskbarState(); - TaskbarState(const uint64_t dispatchTypesState, const uint64_t progress); + TaskbarState(const winrt::Microsoft::Terminal::Control::TaskbarState state, const uint64_t progress); static int ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs); uint64_t Priority() const; - WINRT_PROPERTY(uint64_t, State, 0); + WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::TaskbarState, State, winrt::Microsoft::Terminal::Control::TaskbarState::Clear); WINRT_PROPERTY(uint64_t, Progress, 0); }; } diff --git a/src/cascadia/TerminalApp/TaskbarState.idl b/src/cascadia/TerminalApp/TaskbarState.idl index 159242a17b..9734e5b609 100644 --- a/src/cascadia/TerminalApp/TaskbarState.idl +++ b/src/cascadia/TerminalApp/TaskbarState.idl @@ -6,9 +6,9 @@ namespace TerminalApp [default_interface] runtimeclass TaskbarState { TaskbarState(); - TaskbarState(UInt64 dispatchTypesState, UInt64 progress); + TaskbarState(Microsoft.Terminal.Control.TaskbarState state, UInt64 progress); - UInt64 State{ get; }; + Microsoft.Terminal.Control.TaskbarState State{ get; }; UInt64 Progress{ get; }; UInt64 Priority { get; }; } diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 0e84424ce1..e4760e22a1 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -36,7 +36,6 @@ namespace winrt::TerminalApp::implementation _controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &TerminalPaneContent::_controlConnectionStateChangedHandler }); _controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlWarningBellHandler }); _controlEvents._PromptStarted = _control.PromptStarted(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlPromptStartedHandler }); - _controlEvents._OutputStarted = _control.OutputStarted(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlOutputStartedHandler }); _controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_closeTerminalRequestedHandler }); _controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_restartTerminalRequestedHandler }); @@ -178,28 +177,14 @@ namespace winrt::TerminalApp::implementation TaskbarProgressChanged.raise(*this, nullptr); } - uint64_t TerminalPaneContent::TaskbarState() + winrt::Microsoft::Terminal::Control::TaskbarState TerminalPaneContent::TaskbarState() { - const auto vtState = _control.TaskbarState(); - if (vtState != 0) - { - return vtState; - } - if (_autoDetectActive) - { - return 3; // Indeterminate - } - return 0; + return _control.TaskbarState(); } uint64_t TerminalPaneContent::TaskbarProgress() { - const auto vtState = _control.TaskbarState(); - if (vtState != 0) - { - return _control.TaskbarProgress(); - } - return 0; + return _control.TaskbarProgress(); } void TerminalPaneContent::_controlReadOnlyChanged(const IInspectable&, const IInspectable&) { @@ -340,32 +325,11 @@ namespace winrt::TerminalApp::implementation if (_profile) { const auto notifyStyle = _profile.NotifyOnNextPrompt(); - if (static_cast(notifyStyle) != 0) + if (notifyStyle != OutputNotificationStyle::None) { NotificationRequested.raise(*this, *winrt::make_self(notifyStyle, true)); } - - const auto autoDetect = _profile.AutoDetectRunningCommand(); - if (autoDetect != AutoDetectRunningCommand::Disabled && _autoDetectActive) - { - _autoDetectActive = false; - TaskbarProgressChanged.raise(*this, nullptr); - } - } - } - - void TerminalPaneContent::_controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) - { - if (_profile) - { - const auto autoDetect = _profile.AutoDetectRunningCommand(); - if (autoDetect != AutoDetectRunningCommand::Disabled && !_autoDetectActive) - { - _autoDetectActive = true; - TaskbarProgressChanged.raise(*this, nullptr); - } } } @@ -375,7 +339,7 @@ namespace winrt::TerminalApp::implementation if (_profile) { const auto notifyStyle = _profile.NotifyOnInactiveOutput(); - if (static_cast(notifyStyle) != 0) + if (notifyStyle != OutputNotificationStyle::None) { NotificationRequested.raise(*this, *winrt::make_self(notifyStyle, true)); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index e5c7db221d..f09c09d36c 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -57,7 +57,7 @@ namespace winrt::TerminalApp::implementation } winrt::hstring Title() { return _control.Title(); } - uint64_t TaskbarState(); + winrt::Microsoft::Terminal::Control::TaskbarState TaskbarState(); uint64_t TaskbarProgress(); bool ReadOnly() { return _control.ReadOnly(); } winrt::hstring Icon() const; @@ -80,14 +80,12 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; bool _bellPlayerCreated{ false }; - std::atomic _autoDetectActive{ false }; struct ControlEventTokens { winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; winrt::Microsoft::Terminal::Control::TermControl::PromptStarted_revoker _PromptStarted; - winrt::Microsoft::Terminal::Control::TermControl::OutputStarted_revoker _OutputStarted; winrt::Microsoft::Terminal::Control::TermControl::OutputIdle_revoker _OutputIdle; winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; @@ -108,7 +106,6 @@ namespace winrt::TerminalApp::implementation void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); void _controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); - void _controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); void _controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); void _controlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index f49655dd63..8aa61ebb4b 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -919,6 +919,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation _hasUnfocusedAppearance = static_cast(newAppearance); _unfocusedAppearance = _hasUnfocusedAppearance ? newAppearance : settings; + // Cache the auto-detect setting in an atomic so the off-thread output/prompt + // callbacks can read it without synchronizing with _settings. If the effective + // taskbar state changes (because a command is currently active and the setting + // toggled), notify listeners. + const auto nowEnabled = _settings.AutoDetectRunningCommand() != AutoDetectRunningCommand::Disabled; + const auto wasEnabled = _autoDetectEnabled.exchange(nowEnabled, std::memory_order_relaxed); + if (wasEnabled != nowEnabled && _commandActive.load(std::memory_order_relaxed)) + { + TaskbarProgressChanged.raise(*this, nullptr); + } + const auto lock = _terminal->LockForWriting(); _builtinGlyphs = _settings.EnableBuiltinGlyphs(); @@ -1553,10 +1564,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Gets the internal taskbar state value // Return Value: // - The taskbar state of this control - const size_t ControlCore::TaskbarState() const noexcept + const Control::TaskbarState ControlCore::TaskbarState() const noexcept { const auto lock = _terminal->LockForReading(); - return _terminal->GetTaskbarState(); + const auto vtState = static_cast(_terminal->GetTaskbarState()); + if (vtState == Control::TaskbarState::Clear && + _autoDetectEnabled.load(std::memory_order_relaxed) && + _commandActive.load(std::memory_order_relaxed)) + { + return Control::TaskbarState::Indeterminate; + } + return vtState; } // Method Description: @@ -1615,6 +1633,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation { return; } + if (_commandActive.exchange(false, std::memory_order_relaxed) && + _autoDetectEnabled.load(std::memory_order_relaxed)) + { + TaskbarProgressChanged.raise(*this, nullptr); + } PromptStarted.raise(*this, nullptr); } @@ -1624,6 +1647,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation { return; } + if (!_commandActive.exchange(true, std::memory_order_relaxed) && + _autoDetectEnabled.load(std::memory_order_relaxed)) + { + TaskbarProgressChanged.raise(*this, nullptr); + } OutputStarted.raise(*this, nullptr); } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 686f07b358..57d343b2a0 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -161,7 +161,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void OpenCWD(); #pragma region ICoreState - const size_t TaskbarState() const noexcept; + const Control::TaskbarState TaskbarState() const noexcept; const size_t TaskbarProgress() const noexcept; hstring Title(); @@ -424,6 +424,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation uint16_t _lastHoveredId{ 0 }; std::atomic _initializedTerminal{ false }; std::atomic _restoring{ false }; + std::atomic _autoDetectEnabled{ false }; + std::atomic _commandActive{ false }; bool _isReadOnly{ false }; bool _closing{ false }; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 8139a06db9..692bda162f 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -32,6 +32,7 @@ namespace Microsoft.Terminal.Control [flags] enum OutputNotificationStyle { + None = 0, Taskbar = 0x1, Audible = 0x2, Tab = 0x4, @@ -46,6 +47,19 @@ namespace Microsoft.Terminal.Control Progress }; + // Mirrors Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState + // (which is shared with conhost and lives outside the WinRT projection). + // Values must remain numerically identical so the conversion at the + // ControlCore boundary is a 1:1 cast. + enum TaskbarState + { + Clear = 0, + Set = 1, + Error = 2, + Indeterminate = 3, + Paused = 4, + }; + // Class Description: // TerminalSettings encapsulates all settings that control the // TermControl's behavior. In these settings there is both the entirety diff --git a/src/cascadia/TerminalControl/ICoreState.idl b/src/cascadia/TerminalControl/ICoreState.idl index f03d0391ca..6eb2bf8b7b 100644 --- a/src/cascadia/TerminalControl/ICoreState.idl +++ b/src/cascadia/TerminalControl/ICoreState.idl @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "IControlSettings.idl"; + namespace Microsoft.Terminal.Control { enum MarkCategory @@ -31,7 +33,7 @@ namespace Microsoft.Terminal.Control interface ICoreState { String Title { get; }; - UInt64 TaskbarState { get; }; + Microsoft.Terminal.Control.TaskbarState TaskbarState { get; }; UInt64 TaskbarProgress { get; }; String WorkingDirectory { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index d550106d74..40c277c164 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3358,7 +3358,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Gets the internal taskbar state value // Return Value: // - The taskbar state of this control - const uint64_t TermControl::TaskbarState() const noexcept + const Control::TaskbarState TermControl::TaskbarState() const noexcept { return _core.TaskbarState(); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 9080485758..125b7e20e2 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -86,7 +86,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ColorSelection(Control::SelectionColor fg, Control::SelectionColor bg, Core::MatchMode matchMode); #pragma region ICoreState - const uint64_t TaskbarState() const noexcept; + const Control::TaskbarState TaskbarState() const noexcept; const uint64_t TaskbarProgress() const noexcept; hstring Title(); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index ed3541e8b7..a2246ec421 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1253,7 +1253,7 @@ const std::optional Terminal::GetTabColor() const // - Gets the internal taskbar state value // Return Value: // - The taskbar state -const size_t Microsoft::Terminal::Core::Terminal::GetTaskbarState() const noexcept +const Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState Microsoft::Terminal::Core::Terminal::GetTaskbarState() const noexcept { return _taskbarState; } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 52a449383c..3c5c227cc4 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -245,7 +245,7 @@ public: const std::optional GetTabColor() const; - const size_t GetTaskbarState() const noexcept; + const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState GetTaskbarState() const noexcept; const size_t GetTaskbarProgress() const noexcept; void ColorSelection(const TextAttribute& attr, winrt::Microsoft::Terminal::Core::MatchMode matchMode); @@ -367,6 +367,9 @@ private: til::enumset _systemMode{ Mode::AutoWrap }; + ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState _taskbarState{ ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState::Clear }; + size_t _taskbarProgress = 0; + bool _focused = false; bool _snapOnInput = true; bool _altGrAliasing = true; @@ -375,9 +378,6 @@ private: bool _autoMarkPrompts = false; bool _rainbowSuggestions = false; - size_t _taskbarState = 0; - size_t _taskbarProgress = 0; - size_t _hyperlinkPatternId = 0; std::wstring _answerbackMessage; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 1b67e45dcb..27e75133cc 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -162,7 +162,7 @@ void Terminal::SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::D { _assertLocked(); - _taskbarState = static_cast(state); + _taskbarState = state; switch (state) { diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 753372cd89..198fcc2b25 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -663,43 +663,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { return RS_(L"Profile_OutputNotificationStyleAll/Content"); } - else if (style == static_cast(0)) + else if (style == Ons::None) { return RS_(L"Profile_OutputNotificationStyleNone/Content"); } - std::vector resultList; - resultList.reserve(4); - if (WI_IsFlagSet(style, Ons::Taskbar)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTaskbar/Content")); - } - if (WI_IsFlagSet(style, Ons::Audible)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleAudible/Content")); - } - if (WI_IsFlagSet(style, Ons::Tab)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTab/Content")); - } - if (WI_IsFlagSet(style, Ons::Notification)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleNotification/Content")); - } + std::wstring result; + const auto appendIfFlagSet = [&](Ons flag, std::wstring_view resource) { + // WI_IsFlagSet requires a compile-time constant flag; `flag` is a runtime parameter here. + if ((WI_EnumValue(style) & WI_EnumValue(flag)) != 0) + { + if (!result.empty()) + { + result.append(L", "); + } + result.append(resource); + } + }; - hstring result{}; - for (auto&& entry : resultList) - { - if (result.empty()) - { - result = entry; - } - else - { - result = result + L", " + entry; - } - } - return result; + appendIfFlagSet(Ons::Taskbar, RS_(L"Profile_OutputNotificationStyleTaskbar/Content")); + appendIfFlagSet(Ons::Audible, RS_(L"Profile_OutputNotificationStyleAudible/Content")); + appendIfFlagSet(Ons::Tab, RS_(L"Profile_OutputNotificationStyleTab/Content")); + appendIfFlagSet(Ons::Notification, RS_(L"Profile_OutputNotificationStyleNotification/Content")); + + return hstring{ result }; } bool ProfileViewModel::IsNotifyOnInactiveOutputFlagSet(const uint32_t flag) @@ -745,43 +732,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { return RS_(L"Profile_OutputNotificationStyleAll/Content"); } - else if (style == static_cast(0)) + else if (style == Ons::None) { return RS_(L"Profile_OutputNotificationStyleNone/Content"); } - std::vector resultList; - resultList.reserve(4); - if (WI_IsFlagSet(style, Ons::Taskbar)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTaskbar/Content")); - } - if (WI_IsFlagSet(style, Ons::Audible)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleAudible/Content")); - } - if (WI_IsFlagSet(style, Ons::Tab)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleTab/Content")); - } - if (WI_IsFlagSet(style, Ons::Notification)) - { - resultList.emplace_back(RS_(L"Profile_OutputNotificationStyleNotification/Content")); - } + std::wstring result; + const auto appendIfFlagSet = [&](Ons flag, std::wstring_view resource) { + // WI_IsFlagSet requires a compile-time constant flag; `flag` is a runtime parameter here. + if ((WI_EnumValue(style) & WI_EnumValue(flag)) != 0) + { + if (!result.empty()) + { + result.append(L", "); + } + result.append(resource); + } + }; - hstring result{}; - for (auto&& entry : resultList) - { - if (result.empty()) - { - result = entry; - } - else - { - result = result + L", " + entry; - } - } - return result; + appendIfFlagSet(Ons::Taskbar, RS_(L"Profile_OutputNotificationStyleTaskbar/Content")); + appendIfFlagSet(Ons::Audible, RS_(L"Profile_OutputNotificationStyleAudible/Content")); + appendIfFlagSet(Ons::Tab, RS_(L"Profile_OutputNotificationStyleTab/Content")); + appendIfFlagSet(Ons::Notification, RS_(L"Profile_OutputNotificationStyleNotification/Content")); + + return hstring{ result }; } bool ProfileViewModel::IsNotifyOnNextPromptFlagSet(const uint32_t flag) diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 51e53f1738..5af3b72f04 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -265,66 +265,68 @@ void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() auto& stateMachine = *(term._stateMachine); + using TaskbarState = ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState; + // Initial values for taskbar state and progress should be 0 - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Clear); 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\x1b\\"); - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(1)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Set); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(50)); // Reset to 0 stateMachine.ProcessString(L"\x1b]9;4;0;0\x1b\\"); - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Clear); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); // Set an out of bounds value for state stateMachine.ProcessString(L"\x1b]9;4;5;50\x1b\\"); // Nothing should have changed (dispatch should have returned false) - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Clear); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); // Set an out of bounds value for progress stateMachine.ProcessString(L"\x1b]9;4;1;999\x1b\\"); // Progress should have been clamped to 100 - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(1)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Set); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(100)); // Don't specify any params stateMachine.ProcessString(L"\x1b]9;4\x1b\\"); // State and progress should both be reset to 0 - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Clear); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); // Specify additional params stateMachine.ProcessString(L"\x1b]9;4;1;80;123\x1b\\"); // Additional params should be ignored, state and progress still set normally - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(1)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Set); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(80)); // Edge cases + trailing semicolon testing stateMachine.ProcessString(L"\x1b]9;4;2;\x1b\\"); // String should be processed correctly despite the trailing semicolon, // taskbar progress should remain unchanged from previous value - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(2)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Error); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(80)); stateMachine.ProcessString(L"\x1b]9;4;3;75\x1b\\"); // Given progress value should be ignored because this is the indeterminate state, // so the progress value should remain unchanged - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(3)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Indeterminate); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(80)); stateMachine.ProcessString(L"\x1b]9;4;0;50\x1b\\"); // Taskbar progress should be 0 (the given value should be ignored) - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(0)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Clear); VERIFY_ARE_EQUAL(term.GetTaskbarProgress(), gsl::narrow(0)); stateMachine.ProcessString(L"\x1b]9;4;2;\x1b\\"); // String should be processed correctly despite the trailing semicolon, // taskbar progress should be set to a 'minimum', non-zero value - VERIFY_ARE_EQUAL(term.GetTaskbarState(), gsl::narrow(2)); + VERIFY_ARE_EQUAL(term.GetTaskbarState(), TaskbarState::Error); VERIFY_IS_GREATER_THAN(term.GetTaskbarProgress(), gsl::narrow(0)); } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 0fa2f61ae4..b6cec902c2 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -106,7 +106,7 @@ void AppHost::SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& if (_windowLogic) { const auto state = _windowLogic.TaskbarState(); - _window->SetTaskbarProgress(gsl::narrow_cast(state.State()), + _window->SetTaskbarProgress(state.State(), gsl::narrow_cast(state.Progress())); } } diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index e627bd4495..c78f9d1d92 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -930,27 +930,28 @@ void IslandWindow::FlashTaskbar() // Arguments: // - state: indicates the progress state // - progress: indicates the progress value -void IslandWindow::SetTaskbarProgress(const size_t state, const size_t progress) +void IslandWindow::SetTaskbarProgress(const winrt::Microsoft::Terminal::Control::TaskbarState state, const size_t progress) { if (_taskbar) { + using TbState = winrt::Microsoft::Terminal::Control::TaskbarState; switch (state) { - case 0: + case TbState::Clear: // removes the taskbar progress indicator _taskbar->SetProgressState(_window.get(), TBPF_NOPROGRESS); break; - case 1: + case TbState::Set: // 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: + case TbState::Error: // sets the progress indicator to an error state _taskbar->SetProgressState(_window.get(), TBPF_ERROR); _taskbar->SetProgressValue(_window.get(), progress, 100); break; - case 3: + case TbState::Indeterminate: // sets the progress indicator to an indeterminate state. // FIRST, set the progress to "no progress". That'll clear out any // progress value from the previous state. Otherwise, a transition @@ -959,7 +960,7 @@ void IslandWindow::SetTaskbarProgress(const size_t state, const size_t progress) _taskbar->SetProgressState(_window.get(), TBPF_NOPROGRESS); _taskbar->SetProgressState(_window.get(), TBPF_INDETERMINATE); break; - case 4: + case TbState::Paused: // sets the progress indicator to a pause state _taskbar->SetProgressState(_window.get(), TBPF_PAUSED); _taskbar->SetProgressValue(_window.get(), progress, 100); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 83afeaf44f..439e4d9a0b 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -53,7 +53,7 @@ public: virtual void SetShowTabsFullscreen(const bool newShowTabsFullscreen); void FlashTaskbar(); - void SetTaskbarProgress(const size_t state, const size_t progress); + void SetTaskbarProgress(const winrt::Microsoft::Terminal::Control::TaskbarState state, const size_t progress); void SummonWindow(winrt::TerminalApp::SummonWindowBehavior args); From a07dba52ef79c4de240cbbaa8222b9af75d8f15d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 29 Apr 2026 19:07:09 -0700 Subject: [PATCH 12/24] fix build --- src/terminal/adapter/ut_adapter/adapterTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 596c9ed1a2..fc5728eda6 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -216,7 +216,7 @@ public: Log::Comment(L"NotifyBufferRotation MOCK called..."); } - void NotifyShellIntegrationMark() override + void NotifyShellIntegrationMark(ShellIntegrationMark /*mark*/) override { Log::Comment(L"NotifyShellIntegrationMark MOCK called..."); } From 234838075f878a7fbbed2e78db9d012b8fcdfcd4 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 4 May 2026 12:17:53 -0700 Subject: [PATCH 13/24] notifyOnInactiveOutput --> notifyOnActivity --- doc/cascadia/profiles.schema.json | 2 +- .../TerminalApp/TerminalPaneContent.cpp | 2 +- .../TerminalControl/IControlSettings.idl | 2 +- .../TerminalSettings.cpp | 2 +- .../ProfileViewModel.cpp | 49 +++++++------- .../TerminalSettingsEditor/ProfileViewModel.h | 16 ++--- .../ProfileViewModel.idl | 14 ++-- .../Profiles_Advanced.xaml | 22 +++---- .../Resources/en-US/Resources.resw | 10 +-- .../TerminalSettingsModel/IInheritable.h | 4 +- .../TerminalSettingsModel/MTSMSettings.h | 66 +++++++++---------- .../TerminalSettingsModel/Profile.idl | 2 +- src/cascadia/inc/ControlProperties.h | 2 +- 13 files changed, 99 insertions(+), 94 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 5d64388e6b..502fc70bb9 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2862,7 +2862,7 @@ "description": "Sets the sound played when the application emits a BEL. When set to an array, the terminal will pick one of those sounds at random.", "$ref": "#/$defs/BellSound" }, - "notifyOnInactiveOutput": { + "notifyOnActivity": { "oneOf": [ { "type": "boolean" diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index d07f089d71..4572c6c5ac 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -340,7 +340,7 @@ namespace winrt::TerminalApp::implementation { if (_profile) { - const auto notifyStyle = _profile.NotifyOnInactiveOutput(); + const auto notifyStyle = _profile.NotifyOnActivity(); if (notifyStyle != OutputNotificationStyle::None) { NotificationRequested.raise(*this, diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 692bda162f..e036224f97 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -109,7 +109,7 @@ namespace Microsoft.Terminal.Control PathTranslationStyle PathTranslationStyle { get; }; String DragDropDelimiter { get; }; - OutputNotificationStyle NotifyOnInactiveOutput { get; }; + OutputNotificationStyle NotifyOnActivity { get; }; OutputNotificationStyle NotifyOnNextPrompt { get; }; AutoDetectRunningCommand AutoDetectRunningCommand { get; }; diff --git a/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp b/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp index e14c0dca45..240520a486 100644 --- a/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp @@ -354,7 +354,7 @@ namespace winrt::Microsoft::Terminal::Settings _AllowVtClipboardWrite = profile.AllowVtClipboardWrite(); _PathTranslationStyle = profile.PathTranslationStyle(); _DragDropDelimiter = profile.DragDropDelimiter(); - _NotifyOnInactiveOutput = profile.NotifyOnInactiveOutput(); + _NotifyOnActivity = profile.NotifyOnActivity(); _NotifyOnNextPrompt = profile.NotifyOnNextPrompt(); _AutoDetectRunningCommand = profile.AutoDetectRunningCommand(); } diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index de9780118d..1600a308c1 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -107,9 +107,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _NotifyChanges(L"CurrentPathTranslationStyle"); } - else if (viewModelProperty == L"NotifyOnInactiveOutput") + else if (viewModelProperty == L"NotifyOnActivity") { - _NotifyChanges(L"IsNotifyOnInactiveOutputFlagSet", L"NotifyOnInactiveOutputPreview"); + _NotifyChanges(L"IsNotifyOnActivityFlagSet", L"NotifyOnActivityPreview"); } else if (viewModelProperty == L"NotifyOnNextPrompt") { @@ -584,6 +584,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return iconPath.empty() || iconPath == IconPicker::HideIconValue; } +#pragma region BellStyle hstring ProfileViewModel::BellStylePreview() const { const auto bellStyle = BellStyle(); @@ -663,13 +664,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WI_UpdateFlag(currentStyle, Model::BellStyle::Notification, winrt::unbox_value(on)); BellStyle(currentStyle); } +#pragma endregion - // ===================== NotifyOnInactiveOutput ===================== - - hstring ProfileViewModel::NotifyOnInactiveOutputPreview() const +#pragma region NotifyOnActivity + hstring ProfileViewModel::NotifyOnActivityPreview() const { using Ons = Control::OutputNotificationStyle; - const auto style = NotifyOnInactiveOutput(); + const auto style = NotifyOnActivity(); if (WI_AreAllFlagsSet(style, Ons::Taskbar | Ons::Audible | Ons::Tab | Ons::Notification)) { return RS_(L"Profile_OutputNotificationStyleAll/Content"); @@ -700,41 +701,41 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return hstring{ result }; } - bool ProfileViewModel::IsNotifyOnInactiveOutputFlagSet(const uint32_t flag) + bool ProfileViewModel::IsNotifyOnActivityFlagSet(const uint32_t flag) { - return (WI_EnumValue(NotifyOnInactiveOutput()) & flag) == flag; + return (WI_EnumValue(NotifyOnActivity()) & flag) == flag; } - void ProfileViewModel::SetNotifyOnInactiveOutputTaskbar(winrt::Windows::Foundation::IReference on) + void ProfileViewModel::SetNotifyOnActivityTaskbar(winrt::Windows::Foundation::IReference on) { - auto currentStyle = NotifyOnInactiveOutput(); + auto currentStyle = NotifyOnActivity(); WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Taskbar, winrt::unbox_value(on)); - NotifyOnInactiveOutput(currentStyle); + NotifyOnActivity(currentStyle); } - void ProfileViewModel::SetNotifyOnInactiveOutputAudible(winrt::Windows::Foundation::IReference on) + void ProfileViewModel::SetNotifyOnActivityAudible(winrt::Windows::Foundation::IReference on) { - auto currentStyle = NotifyOnInactiveOutput(); + auto currentStyle = NotifyOnActivity(); WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Audible, winrt::unbox_value(on)); - NotifyOnInactiveOutput(currentStyle); + NotifyOnActivity(currentStyle); } - void ProfileViewModel::SetNotifyOnInactiveOutputTab(winrt::Windows::Foundation::IReference on) + void ProfileViewModel::SetNotifyOnActivityTab(winrt::Windows::Foundation::IReference on) { - auto currentStyle = NotifyOnInactiveOutput(); + auto currentStyle = NotifyOnActivity(); WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Tab, winrt::unbox_value(on)); - NotifyOnInactiveOutput(currentStyle); + NotifyOnActivity(currentStyle); } - void ProfileViewModel::SetNotifyOnInactiveOutputNotification(winrt::Windows::Foundation::IReference on) + void ProfileViewModel::SetNotifyOnActivityNotification(winrt::Windows::Foundation::IReference on) { - auto currentStyle = NotifyOnInactiveOutput(); + auto currentStyle = NotifyOnActivity(); WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Notification, winrt::unbox_value(on)); - NotifyOnInactiveOutput(currentStyle); + NotifyOnActivity(currentStyle); } +#pragma endregion - // ===================== NotifyOnNextPrompt ===================== - +#pragma region NotifyOnNextPrompt hstring ProfileViewModel::NotifyOnNextPromptPreview() const { using Ons = Control::OutputNotificationStyle; @@ -801,6 +802,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Notification, winrt::unbox_value(on)); NotifyOnNextPrompt(currentStyle); } +#pragma endregion + +#pragma region BellSound // Method Description: // - Construct _CurrentBellSounds by importing the _inherited_ value from the model @@ -939,6 +943,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _NotifyChanges(L"CurrentBellSounds"); } } +#pragma endregion void ProfileViewModel::DeleteProfile() { diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 4e21bd0efb..53a6ae0276 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -48,13 +48,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on); void SetBellStyleNotification(winrt::Windows::Foundation::IReference on); - // notify on inactive output bits - hstring NotifyOnInactiveOutputPreview() const; - bool IsNotifyOnInactiveOutputFlagSet(const uint32_t flag); - void SetNotifyOnInactiveOutputTaskbar(winrt::Windows::Foundation::IReference on); - void SetNotifyOnInactiveOutputAudible(winrt::Windows::Foundation::IReference on); - void SetNotifyOnInactiveOutputTab(winrt::Windows::Foundation::IReference on); - void SetNotifyOnInactiveOutputNotification(winrt::Windows::Foundation::IReference on); + // notify on activity bits + hstring NotifyOnActivityPreview() const; + bool IsNotifyOnActivityFlagSet(const uint32_t flag); + void SetNotifyOnActivityTaskbar(winrt::Windows::Foundation::IReference on); + void SetNotifyOnActivityAudible(winrt::Windows::Foundation::IReference on); + void SetNotifyOnActivityTab(winrt::Windows::Foundation::IReference on); + void SetNotifyOnActivityNotification(winrt::Windows::Foundation::IReference on); // notify on next prompt bits hstring NotifyOnNextPromptPreview() const; @@ -163,7 +163,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile, RainbowSuggestions); OBSERVABLE_PROJECTED_SETTING(_profile, PathTranslationStyle); OBSERVABLE_PROJECTED_SETTING(_profile, DragDropDelimiter); - OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnInactiveOutput); + OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnActivity); OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnNextPrompt); OBSERVABLE_PROJECTED_SETTING(_profile, AutoDetectRunningCommand); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index 6b5001f167..65a2c1904a 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -51,12 +51,12 @@ namespace Microsoft.Terminal.Settings.Editor void SetBellStyleTaskbar(Windows.Foundation.IReference on); void SetBellStyleNotification(Windows.Foundation.IReference on); - String NotifyOnInactiveOutputPreview { get; }; - Boolean IsNotifyOnInactiveOutputFlagSet(UInt32 flag); - void SetNotifyOnInactiveOutputTaskbar(Windows.Foundation.IReference on); - void SetNotifyOnInactiveOutputAudible(Windows.Foundation.IReference on); - void SetNotifyOnInactiveOutputTab(Windows.Foundation.IReference on); - void SetNotifyOnInactiveOutputNotification(Windows.Foundation.IReference on); + String NotifyOnActivityPreview { get; }; + Boolean IsNotifyOnActivityFlagSet(UInt32 flag); + void SetNotifyOnActivityTaskbar(Windows.Foundation.IReference on); + void SetNotifyOnActivityAudible(Windows.Foundation.IReference on); + void SetNotifyOnActivityTab(Windows.Foundation.IReference on); + void SetNotifyOnActivityNotification(Windows.Foundation.IReference on); String NotifyOnNextPromptPreview { get; }; Boolean IsNotifyOnNextPromptFlagSet(UInt32 flag); @@ -159,7 +159,7 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle); OBSERVABLE_PROJECTED_PROFILE_SETTING(String, DragDropDelimiter); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtClipboardWrite); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnInactiveOutput); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnActivity); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand); } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 8b25d9deda..eba3c57884 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -198,23 +198,23 @@ - - + + IsChecked="{x:Bind Profile.IsNotifyOnActivityFlagSet(1), BindBack=Profile.SetNotifyOnActivityTaskbar, Mode=TwoWay}" /> + IsChecked="{x:Bind Profile.IsNotifyOnActivityFlagSet(2), BindBack=Profile.SetNotifyOnActivityAudible, Mode=TwoWay}" /> + IsChecked="{x:Bind Profile.IsNotifyOnActivityFlagSet(4), BindBack=Profile.SetNotifyOnActivityTab, Mode=TwoWay}" /> + IsChecked="{x:Bind Profile.IsNotifyOnActivityFlagSet(8), BindBack=Profile.SetNotifyOnActivityNotification, Mode=TwoWay}" /> diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 745a64e502..389522343d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -1577,13 +1577,13 @@ Desktop notification An option to choose from for the notification style setting. Sends a Windows desktop notification (toast). - - Notify on inactive tab output - Header for a control to set the notification style when an inactive tab produces output. + + Notify on activity + Header for a control to set the notification style when a background tab has new activity (output). - + Choose how to be notified when a background tab produces new output. - Help text for the notify on inactive output setting. + Help text for the notify on activity setting. Notify on next prompt diff --git a/src/cascadia/TerminalSettingsModel/IInheritable.h b/src/cascadia/TerminalSettingsModel/IInheritable.h index aae47f61db..f25e3e81bb 100644 --- a/src/cascadia/TerminalSettingsModel/IInheritable.h +++ b/src/cascadia/TerminalSettingsModel/IInheritable.h @@ -136,7 +136,7 @@ private: \ return std::nullopt; \ } \ \ - auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \ + auto _get##name##OverrideSourceImpl() -> decltype(get_strong()) \ { \ /*we have a value*/ \ if (_##name) \ @@ -159,7 +159,7 @@ private: \ } \ \ auto _get##name##OverrideSourceAndValueImpl() \ - ->std::pair \ + -> std::pair \ { \ /*we have a value*/ \ if (_##name) \ diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 9783ebd002..3dde50bf91 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -79,39 +79,39 @@ Author(s): // * TerminalSettings.cpp: TerminalSettings::_ApplyProfileSettings // * IControlSettings.idl or ICoreSettings.idl // * ControlProperties.h -#define MTSM_PROFILE_SETTINGS(X) \ - X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ - X(bool, SnapOnInput, "snapOnInput", true) \ - X(bool, AltGrAliasing, "altGrAliasing", true) \ - X(hstring, AnswerbackMessage, "answerbackMessage") \ - X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ - X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ - X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(hstring, StartingDirectory, "startingDirectory") \ - X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ - X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ - X(guid, ConnectionType, "connectionType") \ - X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ - X(hstring, TabTitle, "tabTitle") \ - X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ - X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ - X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ - X(bool, Elevate, "elevate", false) \ - X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ - X(bool, ShowMarks, "showMarksOnScrollbar", false) \ - X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ - X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ - X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ - X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ - X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ - X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ - X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ - X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ - X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ - X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ - X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnInactiveOutput, "notifyOnInactiveOutput", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ - X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ +#define MTSM_PROFILE_SETTINGS(X) \ + X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ + X(bool, SnapOnInput, "snapOnInput", true) \ + X(bool, AltGrAliasing, "altGrAliasing", true) \ + X(hstring, AnswerbackMessage, "answerbackMessage") \ + X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ + X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ + X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ + X(hstring, StartingDirectory, "startingDirectory") \ + X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ + X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ + X(guid, ConnectionType, "connectionType") \ + X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ + X(hstring, TabTitle, "tabTitle") \ + X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ + X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ + X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ + X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ + X(bool, Elevate, "elevate", false) \ + X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ + X(bool, ShowMarks, "showMarksOnScrollbar", false) \ + X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ + X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ + X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ + X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ + X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ + X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ + X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ + X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ + X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ + X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ + X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, "notifyOnActivity", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ + X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ X(Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand, "autoDetectRunningCommand", Microsoft::Terminal::Control::AutoDetectRunningCommand::Disabled) // Intentionally omitted Profile settings: diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 1121e43fe3..be6f073982 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -96,7 +96,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle); INHERITABLE_PROFILE_SETTING(String, DragDropDelimiter); - INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnInactiveOutput); + INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnActivity); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand); } diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 02ff6d5abc..be848c0f83 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -88,7 +88,7 @@ X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ X(bool, RightClickContextMenu, false) \ X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ - X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnInactiveOutput, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ + X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ X(winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand, winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand::Disabled) \ X(winrt::hstring, DragDropDelimiter, L" ") From b044fd661d730a70fdb11c0c0c5ee4b1e2ba20b6 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 4 May 2026 12:30:00 -0700 Subject: [PATCH 14/24] psychically debug failed formatter --- src/cascadia/TerminalSettingsModel/IInheritable.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/IInheritable.h b/src/cascadia/TerminalSettingsModel/IInheritable.h index f25e3e81bb..aae47f61db 100644 --- a/src/cascadia/TerminalSettingsModel/IInheritable.h +++ b/src/cascadia/TerminalSettingsModel/IInheritable.h @@ -136,7 +136,7 @@ private: \ return std::nullopt; \ } \ \ - auto _get##name##OverrideSourceImpl() -> decltype(get_strong()) \ + auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \ { \ /*we have a value*/ \ if (_##name) \ @@ -159,7 +159,7 @@ private: \ } \ \ auto _get##name##OverrideSourceAndValueImpl() \ - -> std::pair \ + ->std::pair \ { \ /*we have a value*/ \ if (_##name) \ From 042eb5b1a9aefef85e06127bc09fbaee544eac72 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 4 May 2026 17:55:03 -0700 Subject: [PATCH 15/24] autoDetectRunningCommand --> bool; _autoDetectEnabled --> _autoDetectCommandActivity --- doc/cascadia/profiles.schema.json | 9 ++------- src/cascadia/TerminalControl/ControlCore.cpp | 10 +++++----- src/cascadia/TerminalControl/ControlCore.h | 2 +- src/cascadia/TerminalControl/IControlSettings.idl | 9 +-------- .../TerminalSettingsEditor/ProfileViewModel.cpp | 5 ----- .../TerminalSettingsEditor/ProfileViewModel.h | 1 - .../TerminalSettingsEditor/ProfileViewModel.idl | 5 +---- .../TerminalSettingsEditor/Profiles_Advanced.xaml | 6 ++---- .../Resources/en-US/Resources.resw | 12 ------------ src/cascadia/TerminalSettingsModel/EnumMappings.cpp | 1 - src/cascadia/TerminalSettingsModel/EnumMappings.h | 1 - src/cascadia/TerminalSettingsModel/EnumMappings.idl | 1 - src/cascadia/TerminalSettingsModel/MTSMSettings.h | 2 +- src/cascadia/TerminalSettingsModel/Profile.idl | 2 +- .../TerminalSettingsSerializationHelpers.h | 9 --------- src/cascadia/inc/ControlProperties.h | 2 +- 16 files changed, 15 insertions(+), 62 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 502fc70bb9..789b9c59d9 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2925,14 +2925,9 @@ "description": "Controls how you are notified when a new shell prompt is detected. Requires shell integration." }, "autoDetectRunningCommand": { - "default": "disabled", + "default": false, "description": "Automatically detect when a command is running and show a progress indicator in the tab and taskbar.", - "enum": [ - "disabled", - "automatic", - "progress" - ], - "type": "string" + "type": "boolean" }, "closeOnExit": { "default": "automatic", diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 8aa61ebb4b..e9998bf8e4 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -923,8 +923,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // callbacks can read it without synchronizing with _settings. If the effective // taskbar state changes (because a command is currently active and the setting // toggled), notify listeners. - const auto nowEnabled = _settings.AutoDetectRunningCommand() != AutoDetectRunningCommand::Disabled; - const auto wasEnabled = _autoDetectEnabled.exchange(nowEnabled, std::memory_order_relaxed); + const auto nowEnabled = _settings.AutoDetectRunningCommand(); + const auto wasEnabled = _autoDetectCommandActivity.exchange(nowEnabled, std::memory_order_relaxed); if (wasEnabled != nowEnabled && _commandActive.load(std::memory_order_relaxed)) { TaskbarProgressChanged.raise(*this, nullptr); @@ -1569,7 +1569,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto lock = _terminal->LockForReading(); const auto vtState = static_cast(_terminal->GetTaskbarState()); if (vtState == Control::TaskbarState::Clear && - _autoDetectEnabled.load(std::memory_order_relaxed) && + _autoDetectCommandActivity.load(std::memory_order_relaxed) && _commandActive.load(std::memory_order_relaxed)) { return Control::TaskbarState::Indeterminate; @@ -1634,7 +1634,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } if (_commandActive.exchange(false, std::memory_order_relaxed) && - _autoDetectEnabled.load(std::memory_order_relaxed)) + _autoDetectCommandActivity.load(std::memory_order_relaxed)) { TaskbarProgressChanged.raise(*this, nullptr); } @@ -1648,7 +1648,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } if (!_commandActive.exchange(true, std::memory_order_relaxed) && - _autoDetectEnabled.load(std::memory_order_relaxed)) + _autoDetectCommandActivity.load(std::memory_order_relaxed)) { TaskbarProgressChanged.raise(*this, nullptr); } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 57d343b2a0..8982fddde7 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -424,7 +424,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation uint16_t _lastHoveredId{ 0 }; std::atomic _initializedTerminal{ false }; std::atomic _restoring{ false }; - std::atomic _autoDetectEnabled{ false }; + std::atomic _autoDetectCommandActivity{ false }; std::atomic _commandActive{ false }; bool _isReadOnly{ false }; bool _closing{ false }; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index e036224f97..9eff933234 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -40,13 +40,6 @@ namespace Microsoft.Terminal.Control All = 0xffffffff }; - enum AutoDetectRunningCommand - { - Disabled, - Automatic, - Progress - }; - // Mirrors Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState // (which is shared with conhost and lives outside the WinRT projection). // Values must remain numerically identical so the conversion at the @@ -111,7 +104,7 @@ namespace Microsoft.Terminal.Control OutputNotificationStyle NotifyOnActivity { get; }; OutputNotificationStyle NotifyOnNextPrompt { get; }; - AutoDetectRunningCommand AutoDetectRunningCommand { get; }; + Boolean AutoDetectRunningCommand { get; }; // NOTE! When adding something here, make sure to update ControlProperties.h too! }; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 1600a308c1..55599ec1c1 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -40,7 +40,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle, L"Profile_PathTranslationStyle", L"Content"); - INITIALIZE_BINDABLE_ENUM_SETTING(AutoDetectRunningCommand, AutoDetectRunningCommand, winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand, L"Profile_AutoDetectRunningCommand", L"Content"); _InitializeCurrentBellSounds(); @@ -115,10 +114,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _NotifyChanges(L"IsNotifyOnNextPromptFlagSet", L"NotifyOnNextPromptPreview"); } - else if (viewModelProperty == L"AutoDetectRunningCommand") - { - _NotifyChanges(L"CurrentAutoDetectRunningCommand"); - } else if (viewModelProperty == L"Padding") { _parsedPadding = StringToXamlThickness(_profile.Padding()); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 53a6ae0276..1dcedae3e1 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -173,7 +173,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation GETSET_BINDABLE_ENUM_SETTING(CloseOnExitMode, Microsoft::Terminal::Settings::Model::CloseOnExitMode, CloseOnExit); GETSET_BINDABLE_ENUM_SETTING(ScrollState, Microsoft::Terminal::Control::ScrollbarState, ScrollState); GETSET_BINDABLE_ENUM_SETTING(PathTranslationStyle, Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle); - GETSET_BINDABLE_ENUM_SETTING(AutoDetectRunningCommand, Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand); private: Model::Profile _profile; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index 65a2c1904a..45b94d83c7 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -65,9 +65,6 @@ namespace Microsoft.Terminal.Settings.Editor void SetNotifyOnNextPromptTab(Windows.Foundation.IReference on); void SetNotifyOnNextPromptNotification(Windows.Foundation.IReference on); - IInspectable CurrentAutoDetectRunningCommand; - Windows.Foundation.Collections.IObservableVector AutoDetectRunningCommandList { get; }; - String BellSoundPreview { get; }; Windows.Foundation.Collections.IObservableVector CurrentBellSounds { get; }; void RequestAddBellSound(String path); @@ -161,6 +158,6 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtClipboardWrite); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnActivity); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AutoDetectRunningCommand); } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index eba3c57884..715927b2fd 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -244,10 +244,8 @@ ClearSettingValue="{x:Bind Profile.ClearAutoDetectRunningCommand}" HasSettingValue="{x:Bind Profile.HasAutoDetectRunningCommand, Mode=OneWay}" SettingOverrideSource="{x:Bind Profile.AutoDetectRunningCommandOverrideSource, Mode=OneWay}"> - + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 389522343d..febb9b1b01 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -1601,18 +1601,6 @@ Automatically show a progress indicator when a command is running. Requires shell integration. Help text for the auto-detect running command setting. - - Disabled - An option for the auto-detect running command setting. When selected, automatic command detection is off. - - - Automatic - An option for the auto-detect running command setting. When selected, the terminal automatically detects running commands and shows a progress indicator. - - - Progress - An option for the auto-detect running command setting. When selected, the terminal shows command progress by walking the buffer (not yet implemented). - Desktop notification An option to choose from for the "bell style" setting. When selected, a Windows desktop notification (toast) is sent to notify the user. diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index 91af5fd4ee..b92994c2d6 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -54,7 +54,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::IntenseStyle, IntenseTextStyle); DEFINE_ENUM_MAP(Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors); DEFINE_ENUM_MAP(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle); - DEFINE_ENUM_MAP(Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand); // Actions DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::ResizeDirection, ResizeDirection); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index 6378b715a9..e858670450 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -52,7 +52,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap IntenseTextStyle(); static winrt::Windows::Foundation::Collections::IMap AdjustIndistinguishableColors(); static winrt::Windows::Foundation::Collections::IMap PathTranslationStyle(); - static winrt::Windows::Foundation::Collections::IMap AutoDetectRunningCommand(); // Actions static winrt::Windows::Foundation::Collections::IMap ResizeDirection(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index acfdb492d4..7f52aace49 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -34,7 +34,6 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap FontWeight { get; }; static Windows.Foundation.Collections.IMap IntenseTextStyle { get; }; static Windows.Foundation.Collections.IMap PathTranslationStyle { get; }; - static Windows.Foundation.Collections.IMap AutoDetectRunningCommand { get; }; // Actions static Windows.Foundation.Collections.IMap ResizeDirection { get; }; diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 3dde50bf91..f8cd7e85e5 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -112,7 +112,7 @@ Author(s): X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, "notifyOnActivity", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ - X(Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand, "autoDetectRunningCommand", Microsoft::Terminal::Control::AutoDetectRunningCommand::Disabled) + X(bool, AutoDetectRunningCommand, "autoDetectRunningCommand", false) // Intentionally omitted Profile settings: // * Name diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index be6f073982..91a283c772 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -98,6 +98,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(String, DragDropDelimiter); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnActivity); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); - INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand); + INHERITABLE_PROFILE_SETTING(Boolean, AutoDetectRunningCommand); } } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index c3f268660c..e153a0fd23 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -170,15 +170,6 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Control::OutputNotificationStyle) } }; -JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand) -{ - JSON_MAPPINGS(3) = { - pair_type{ "disabled", ValueType::Disabled }, - pair_type{ "automatic", ValueType::Automatic }, - pair_type{ "progress", ValueType::Progress }, - }; -}; - JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::ConvergedAlignment) { // reduce repetition diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index be848c0f83..ca8b674536 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -90,5 +90,5 @@ X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ - X(winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand, AutoDetectRunningCommand, winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand::Disabled) \ + X(bool, AutoDetectRunningCommand, false) \ X(winrt::hstring, DragDropDelimiter, L" ") From 07a4f3acfc03d0cbacd96d459ef300d6dcc1940e Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 4 May 2026 17:57:55 -0700 Subject: [PATCH 16/24] remove .leading from throttledTaskbarProgressChanged --- src/cascadia/TerminalApp/Tab.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 5a44e0e44c..989649e1c5 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1098,7 +1098,6 @@ namespace winrt::TerminalApp::implementation dispatcher, til::throttled_func_options{ .delay = std::chrono::milliseconds{ 200 }, - .leading = true, .trailing = true, }, [weakThis]() { From 071963420e73569d99b3d761f458ccb8de8470f8 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 4 May 2026 19:00:15 -0700 Subject: [PATCH 17/24] {0} --> ::None; remove _restoring --- src/cascadia/TerminalControl/ControlCore.cpp | 24 +------ src/cascadia/TerminalControl/ControlCore.h | 3 +- .../TerminalSettingsModel/MTSMSettings.h | 66 +++++++++---------- src/cascadia/inc/ControlProperties.h | 62 ++++++++--------- 4 files changed, 66 insertions(+), 89 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index e9998bf8e4..a685e31119 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1620,19 +1620,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Since this can only ever be triggered by output from the connection, // then the Terminal already has the write lock when calling this // callback. - if (_restoring) - { - return; - } WarningBell.raise(*this, nullptr); } void ControlCore::_terminalPromptStarted() { - if (_restoring) - { - return; - } if (_commandActive.exchange(false, std::memory_order_relaxed) && _autoDetectCommandActivity.load(std::memory_order_relaxed)) { @@ -1643,10 +1635,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_terminalOutputStarted() { - if (_restoring) - { - return; - } if (!_commandActive.exchange(true, std::memory_order_relaxed) && _autoDetectCommandActivity.load(std::memory_order_relaxed)) { @@ -1712,10 +1700,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_terminalTaskbarProgressChanged() { - if (_restoring) - { - return; - } TaskbarProgressChanged.raise(*this, nullptr); } @@ -1733,10 +1717,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - duration - How long the note should be sustained (in microseconds). void ControlCore::_terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) { - if (_restoring) - { - return; - } // The UI thread might try to acquire the console lock from time to time. // --> Unlock it, so the UI doesn't hang while we're busy. const auto suspension = _terminal->SuspendLock(); @@ -1909,10 +1889,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->SerializeMainBuffer(handle); } - void ControlCore::RestoreFromPath(const wchar_t* path) + void ControlCore::RestoreFromPath(const wchar_t* path) const { - _restoring = true; - const auto restoreComplete = wil::scope_exit([&] { _restoring = false; }); wil::unique_handle file{ CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr) }; // This block of code exists temporarily to fix buffer dumps that were diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 8982fddde7..c1a28d232b 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -154,7 +154,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void Close(); void PersistTo(HANDLE handle) const; - void RestoreFromPath(const wchar_t* path); + void RestoreFromPath(const wchar_t* path) const; void ClearQuickFix(); @@ -423,7 +423,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::optional _lastHoveredCell; uint16_t _lastHoveredId{ 0 }; std::atomic _initializedTerminal{ false }; - std::atomic _restoring{ false }; std::atomic _autoDetectCommandActivity{ false }; std::atomic _commandActive{ false }; bool _isReadOnly{ false }; diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index f8cd7e85e5..24d5a5ac60 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -79,39 +79,39 @@ Author(s): // * TerminalSettings.cpp: TerminalSettings::_ApplyProfileSettings // * IControlSettings.idl or ICoreSettings.idl // * ControlProperties.h -#define MTSM_PROFILE_SETTINGS(X) \ - X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ - X(bool, SnapOnInput, "snapOnInput", true) \ - X(bool, AltGrAliasing, "altGrAliasing", true) \ - X(hstring, AnswerbackMessage, "answerbackMessage") \ - X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ - X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ - X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(hstring, StartingDirectory, "startingDirectory") \ - X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ - X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ - X(guid, ConnectionType, "connectionType") \ - X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ - X(hstring, TabTitle, "tabTitle") \ - X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ - X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ - X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ - X(bool, Elevate, "elevate", false) \ - X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ - X(bool, ShowMarks, "showMarksOnScrollbar", false) \ - X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ - X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ - X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ - X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ - X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ - X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ - X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ - X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ - X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ - X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ - X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, "notifyOnActivity", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ - X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ +#define MTSM_PROFILE_SETTINGS(X) \ + X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ + X(bool, SnapOnInput, "snapOnInput", true) \ + X(bool, AltGrAliasing, "altGrAliasing", true) \ + X(hstring, AnswerbackMessage, "answerbackMessage") \ + X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ + X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ + X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ + X(hstring, StartingDirectory, "startingDirectory") \ + X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ + X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ + X(guid, ConnectionType, "connectionType") \ + X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ + X(hstring, TabTitle, "tabTitle") \ + X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ + X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ + X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ + X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ + X(bool, Elevate, "elevate", false) \ + X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ + X(bool, ShowMarks, "showMarksOnScrollbar", false) \ + X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ + X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ + X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ + X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ + X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ + X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ + X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ + X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ + X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ + X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ + X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, "notifyOnActivity", Microsoft::Terminal::Control::OutputNotificationStyle::None) \ + X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle::None) \ X(bool, AutoDetectRunningCommand, "autoDetectRunningCommand", false) // Intentionally omitted Profile settings: diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index ca8b674536..fb397035e6 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -60,35 +60,35 @@ // --------------------------- Control Settings --------------------------- // All of these settings are defined in IControlSettings. -#define CONTROL_SETTINGS(X) \ - X(winrt::guid, SessionId) \ - X(bool, EnableUnfocusedAcrylic, false) \ - X(winrt::hstring, Padding, DEFAULT_PADDING) \ - X(winrt::hstring, FontFace, L"Consolas") \ - X(float, FontSize, DEFAULT_FONT_SIZE) \ - X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ - X(IFontFeatureMap, FontFeatures) \ - X(IFontAxesMap, FontAxes) \ - X(bool, EnableBuiltinGlyphs, true) \ - X(bool, EnableColorGlyphs, true) \ - X(winrt::hstring, CellWidth) \ - X(winrt::hstring, CellHeight) \ - X(winrt::hstring, Commandline) \ - X(winrt::hstring, StartingDirectory) \ - X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible) \ - X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI) \ - X(bool, DisablePartialInvalidation, false) \ - X(bool, SoftwareRendering, false) \ - X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement) \ - X(winrt::Microsoft::Terminal::Control::AmbiguousWidth, AmbiguousWidth, winrt::Microsoft::Terminal::Control::AmbiguousWidth::Narrow) \ - X(winrt::Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope::Default) \ - X(bool, UseBackgroundImageForWindow, false) \ - X(bool, ShowMarks, false) \ - X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ - X(bool, RightClickContextMenu, false) \ - X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ - X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ - X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle{ 0 }) \ - X(bool, AutoDetectRunningCommand, false) \ +#define CONTROL_SETTINGS(X) \ + X(winrt::guid, SessionId) \ + X(bool, EnableUnfocusedAcrylic, false) \ + X(winrt::hstring, Padding, DEFAULT_PADDING) \ + X(winrt::hstring, FontFace, L"Consolas") \ + X(float, FontSize, DEFAULT_FONT_SIZE) \ + X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ + X(IFontFeatureMap, FontFeatures) \ + X(IFontAxesMap, FontAxes) \ + X(bool, EnableBuiltinGlyphs, true) \ + X(bool, EnableColorGlyphs, true) \ + X(winrt::hstring, CellWidth) \ + X(winrt::hstring, CellHeight) \ + X(winrt::hstring, Commandline) \ + X(winrt::hstring, StartingDirectory) \ + X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible) \ + X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ + X(winrt::Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI) \ + X(bool, DisablePartialInvalidation, false) \ + X(bool, SoftwareRendering, false) \ + X(winrt::Microsoft::Terminal::Control::TextMeasurement, TextMeasurement) \ + X(winrt::Microsoft::Terminal::Control::AmbiguousWidth, AmbiguousWidth, winrt::Microsoft::Terminal::Control::AmbiguousWidth::Narrow) \ + X(winrt::Microsoft::Terminal::Control::DefaultInputScope, DefaultInputScope, winrt::Microsoft::Terminal::Control::DefaultInputScope::Default) \ + X(bool, UseBackgroundImageForWindow, false) \ + X(bool, ShowMarks, false) \ + X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ + X(bool, RightClickContextMenu, false) \ + X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ + X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::None) \ + X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::None) \ + X(bool, AutoDetectRunningCommand, false) \ X(winrt::hstring, DragDropDelimiter, L" ") From 8f4aa8e635756486173be15b870b665945a2326d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 5 May 2026 17:15:20 -0700 Subject: [PATCH 18/24] add indicator to cmd plt --- src/cascadia/TerminalApp/CommandPalette.xaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index b933173c77..adc38087d8 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -231,6 +231,12 @@ Glyph="" Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).BellIndicator, Mode=OneWay}" /> + + Date: Tue, 5 May 2026 18:08:48 -0700 Subject: [PATCH 19/24] TerminalPaneContent: OutputIdle --> OutputBurstEnded --- src/cascadia/TerminalApp/TerminalPaneContent.cpp | 10 +++++++--- src/cascadia/TerminalApp/TerminalPaneContent.h | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 4572c6c5ac..075fe9a9f1 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -44,7 +44,7 @@ namespace winrt::TerminalApp::implementation _controlEvents._SetTaskbarProgress = _control.SetTaskbarProgress(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlSetTaskbarProgress }); _controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlReadOnlyChanged }); _controlEvents._FocusFollowMouseRequested = _control.FocusFollowMouseRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlFocusFollowMouseRequested }); - _controlEvents._OutputIdle = _control.OutputIdle(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlOutputIdleHandler }); + _controlEvents._OutputBurstEnded = _control.OutputIdle(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlOutputBurstEndedHandler }); } void TerminalPaneContent::_removeControlEvents() { @@ -335,8 +335,12 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPaneContent::_controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + // The underlying TermControl::OutputIdle event is fired on the trailing + // edge of a 100ms-debounced output burst (see ControlCore::Initialize). + // When "notifyOnActivity" is enabled, we get one event per burst of + // output, which naturally coalesces a stream of output into a single notification. + void TerminalPaneContent::_controlOutputBurstEndedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) { if (_profile) { diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index 7bb0385183..af207296ab 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -87,7 +87,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; winrt::Microsoft::Terminal::Control::TermControl::PromptStarted_revoker _PromptStarted; - winrt::Microsoft::Terminal::Control::TermControl::OutputIdle_revoker _OutputIdle; + winrt::Microsoft::Terminal::Control::TermControl::OutputIdle_revoker _OutputBurstEnded; winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; @@ -107,7 +107,7 @@ namespace winrt::TerminalApp::implementation void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); void _controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); - void _controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); + void _controlOutputBurstEndedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); void _controlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); void _controlTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); From 30020752caefb2f04e6d7acb3d4325eee8ab531c Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 5 May 2026 18:29:10 -0700 Subject: [PATCH 20/24] NotificationEventArgs: OnlyWhenInactive --> AlwaysNotify (flip logic) --- src/cascadia/TerminalApp/IPaneContent.idl | 2 +- src/cascadia/TerminalApp/Tab.cpp | 2 +- src/cascadia/TerminalApp/TerminalPaneContent.cpp | 4 ++-- src/cascadia/TerminalApp/TerminalPaneContent.h | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index 4174384475..a0a4e92fd7 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -22,7 +22,7 @@ namespace TerminalApp String Title { get; }; String Body { get; }; Microsoft.Terminal.Control.OutputNotificationStyle Style { get; }; - Boolean OnlyWhenInactive { get; }; + Boolean AlwaysNotify { get; }; }; interface IPaneContent diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 989649e1c5..fe130b456e 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1214,7 +1214,7 @@ namespace winrt::TerminalApp::implementation const auto activeContent = tab->GetActiveContent(); const auto isActivePaneContent = activeContent && activeContent == sender; - if (notifArgs.OnlyWhenInactive() && isActivePaneContent && + if (!notifArgs.AlwaysNotify() && isActivePaneContent && tab->_focusState != WUX::FocusState::Unfocused) { co_return; diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 075fe9a9f1..0623faabf7 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -330,7 +330,7 @@ namespace winrt::TerminalApp::implementation if (notifyStyle != OutputNotificationStyle::None) { NotificationRequested.raise(*this, - *winrt::make_self(notifyStyle, true)); + *winrt::make_self(notifyStyle, false)); } } } @@ -348,7 +348,7 @@ namespace winrt::TerminalApp::implementation if (notifyStyle != OutputNotificationStyle::None) { NotificationRequested.raise(*this, - *winrt::make_self(notifyStyle, true)); + *winrt::make_self(notifyStyle, false)); } } } diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index af207296ab..6bfd0bb9bd 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -24,11 +24,11 @@ namespace winrt::TerminalApp::implementation struct NotificationEventArgs : public NotificationEventArgsT { public: - NotificationEventArgs(winrt::Microsoft::Terminal::Control::OutputNotificationStyle style, bool onlyWhenInactive = false, const winrt::hstring& title = {}, const winrt::hstring& body = {}) : - Style(style), OnlyWhenInactive(onlyWhenInactive), Title(title), Body(body) {} + NotificationEventArgs(winrt::Microsoft::Terminal::Control::OutputNotificationStyle style, bool alwaysNotify = true, const winrt::hstring& title = {}, const winrt::hstring& body = {}) : + Style(style), AlwaysNotify(alwaysNotify), Title(title), Body(body) {} til::property Style; - til::property OnlyWhenInactive; + til::property AlwaysNotify; til::property Title; til::property Body; }; From 5f38be735b64b70ab3f2706b900c0080074fb5b6 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 12 May 2026 18:02:48 -0700 Subject: [PATCH 21/24] add "notifyOnActivityThreshold" & "notifyOnNextPromptThreshold" --- doc/cascadia/profiles.schema.json | 12 +++++++ .../TerminalApp/TerminalPaneContent.cpp | 31 +++++++++++++++++++ .../TerminalApp/TerminalPaneContent.h | 7 +++++ .../TerminalControl/IControlSettings.idl | 2 ++ .../TerminalSettings.cpp | 2 ++ .../TerminalSettingsEditor/ProfileViewModel.h | 2 ++ .../ProfileViewModel.idl | 2 ++ .../Profiles_Advanced.xaml | 28 +++++++++++++++++ .../Resources/en-US/Resources.resw | 24 ++++++++++++++ .../TerminalSettingsModel/MTSMSettings.h | 2 ++ .../TerminalSettingsModel/Profile.idl | 2 ++ src/cascadia/inc/ControlProperties.h | 2 ++ 12 files changed, 116 insertions(+) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 789b9c59d9..e43d3cb207 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2924,6 +2924,18 @@ ], "description": "Controls how you are notified when a new shell prompt is detected. Requires shell integration." }, + "notifyOnActivityThreshold": { + "type": "integer", + "minimum": 0, + "default": 5, + "description": "Minimum number of seconds between consecutive activity notifications for this profile. Use this to suppress repeated notifications from chatty processes. The first notification after the pane has been silent always fires; subsequent notifications within this window are suppressed. Use 0 to always notify." + }, + "notifyOnNextPromptThreshold": { + "type": "integer", + "minimum": 0, + "default": 5, + "description": "Suppress the next-prompt notification unless the just-finished command ran for at least this many seconds. Requires shell integration. Use 0 to always notify." + }, "autoDetectRunningCommand": { "default": false, "description": "Automatically detect when a command is running and show a progress indicator in the tab and taskbar.", diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 0623faabf7..a33ab9fe81 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -44,6 +44,7 @@ namespace winrt::TerminalApp::implementation _controlEvents._SetTaskbarProgress = _control.SetTaskbarProgress(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlSetTaskbarProgress }); _controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlReadOnlyChanged }); _controlEvents._FocusFollowMouseRequested = _control.FocusFollowMouseRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlFocusFollowMouseRequested }); + _controlEvents._OutputStarted = _control.OutputStarted(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlOutputStartedHandler }); _controlEvents._OutputBurstEnded = _control.OutputIdle(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlOutputBurstEndedHandler }); } void TerminalPaneContent::_removeControlEvents() @@ -329,12 +330,32 @@ namespace winrt::TerminalApp::implementation const auto notifyStyle = _profile.NotifyOnNextPrompt(); if (notifyStyle != OutputNotificationStyle::None) { + if (const auto thresholdInSeconds = _profile.NotifyOnNextPromptThreshold(); thresholdInSeconds > 0) + { + if (_lastOutputStartedAt == 0) + { + return; + } + if (const auto elapsedMs = GetTickCount64() - _lastOutputStartedAt; elapsedMs < (static_cast(thresholdInSeconds) * 1000)) + { + _lastOutputStartedAt = 0; + return; + } + } + _lastOutputStartedAt = 0; + NotificationRequested.raise(*this, *winrt::make_self(notifyStyle, false)); } } } + void TerminalPaneContent::_controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*eventArgs*/) + { + _lastOutputStartedAt = GetTickCount64(); + } + // The underlying TermControl::OutputIdle event is fired on the trailing // edge of a 100ms-debounced output burst (see ControlCore::Initialize). // When "notifyOnActivity" is enabled, we get one event per burst of @@ -347,6 +368,16 @@ namespace winrt::TerminalApp::implementation const auto notifyStyle = _profile.NotifyOnActivity(); if (notifyStyle != OutputNotificationStyle::None) { + const auto now = GetTickCount64(); + const auto thresholdSeconds = _profile.NotifyOnActivityThreshold(); + if (thresholdSeconds > 0 && + _lastActivityNotificationAt != 0 && + (now - _lastActivityNotificationAt) < (static_cast(thresholdSeconds) * 1000)) + { + return; + } + _lastActivityNotificationAt = now; + NotificationRequested.raise(*this, *winrt::make_self(notifyStyle, false)); } diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index 6bfd0bb9bd..ee9fdf84b5 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -82,11 +82,17 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; bool _bellPlayerCreated{ false }; + // Tracks the GetTickCount64() for NotifyOnActivityThreshold + // and NotifyOnNextPromptThreshold respectively. + uint64_t _lastActivityNotificationAt{ 0 }; + uint64_t _lastOutputStartedAt{ 0 }; + struct ControlEventTokens { winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; winrt::Microsoft::Terminal::Control::TermControl::PromptStarted_revoker _PromptStarted; + winrt::Microsoft::Terminal::Control::TermControl::OutputStarted_revoker _OutputStarted; winrt::Microsoft::Terminal::Control::TermControl::OutputIdle_revoker _OutputBurstEnded; winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; @@ -107,6 +113,7 @@ namespace winrt::TerminalApp::implementation void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); void _controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); + void _controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); void _controlOutputBurstEndedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& eventArgs); void _controlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 9eff933234..9d585afba2 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -104,6 +104,8 @@ namespace Microsoft.Terminal.Control OutputNotificationStyle NotifyOnActivity { get; }; OutputNotificationStyle NotifyOnNextPrompt { get; }; + Int32 NotifyOnActivityThreshold { get; }; + Int32 NotifyOnNextPromptThreshold { get; }; Boolean AutoDetectRunningCommand { get; }; // NOTE! When adding something here, make sure to update ControlProperties.h too! diff --git a/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp b/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp index 240520a486..36467857d0 100644 --- a/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsAppAdapterLib/TerminalSettings.cpp @@ -356,6 +356,8 @@ namespace winrt::Microsoft::Terminal::Settings _DragDropDelimiter = profile.DragDropDelimiter(); _NotifyOnActivity = profile.NotifyOnActivity(); _NotifyOnNextPrompt = profile.NotifyOnNextPrompt(); + _NotifyOnActivityThreshold = profile.NotifyOnActivityThreshold(); + _NotifyOnNextPromptThreshold = profile.NotifyOnNextPromptThreshold(); _AutoDetectRunningCommand = profile.AutoDetectRunningCommand(); } diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 1dcedae3e1..1ecbf54406 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -165,6 +165,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile, DragDropDelimiter); OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnActivity); OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnNextPrompt); + OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnActivityThreshold); + OBSERVABLE_PROJECTED_SETTING(_profile, NotifyOnNextPromptThreshold); OBSERVABLE_PROJECTED_SETTING(_profile, AutoDetectRunningCommand); WINRT_PROPERTY(bool, IsBaseLayer, false); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index 45b94d83c7..9ab89746d2 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -158,6 +158,8 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtClipboardWrite); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnActivity); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Int32, NotifyOnActivityThreshold); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Int32, NotifyOnNextPromptThreshold); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AutoDetectRunningCommand); } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 715927b2fd..2a106581e8 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -218,6 +218,20 @@ + + + + + + + + + + Choose how to be notified when a background tab produces new output. Help text for the notify on activity setting. + + Minimum seconds between activity notifications + Header for a numeric control that sets the minimum number of seconds between consecutive "notify on activity" notifications. Useful for chatty programs. + + + Suppress repeated activity notifications within this many seconds of the previous one. Set to 0 to always notify. + Help text for the minimum-duration cooldown applied to "notify on activity" notifications. + + + Minimum seconds between activity notifications + Accessible name for a numeric control that sets the minimum number of seconds between consecutive "notify on activity" notifications. + Notify on next prompt Header for a control to set the notification style when a new shell prompt is detected (requires shell integration). @@ -1593,6 +1605,18 @@ Choose how to be notified when a command finishes and a new prompt appears. Requires shell integration. Help text for the notify on next prompt setting. + + Minimum duration for the next prompt notification + Header for a numeric control that sets the minimum number of seconds a command must run before its completion triggers a "notify on next prompt" notification. + + + Only notify when the command ran for at least this many seconds. Requires shell integration. Set to 0 to always notify. + Help text for the minimum-duration threshold applied to "notify on next prompt" notifications. + + + Minimum duration for next prompt notification + Accessible name for a numeric control that sets the minimum number of seconds a command must run before its completion triggers a "notify on next prompt" notification. + Auto-detect running command Header for a control to configure automatic detection of a running command's progress state. diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 24d5a5ac60..b9127f93ec 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -112,6 +112,8 @@ Author(s): X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, "notifyOnActivity", Microsoft::Terminal::Control::OutputNotificationStyle::None) \ X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle::None) \ + X(int32_t, NotifyOnActivityThreshold, "notifyOnActivityThreshold", 5) \ + X(int32_t, NotifyOnNextPromptThreshold, "notifyOnNextPromptThreshold", 5) \ X(bool, AutoDetectRunningCommand, "autoDetectRunningCommand", false) // Intentionally omitted Profile settings: diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 91a283c772..dd5cd4a140 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -98,6 +98,8 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(String, DragDropDelimiter); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnActivity); INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt); + INHERITABLE_PROFILE_SETTING(Int32, NotifyOnActivityThreshold); + INHERITABLE_PROFILE_SETTING(Int32, NotifyOnNextPromptThreshold); INHERITABLE_PROFILE_SETTING(Boolean, AutoDetectRunningCommand); } } diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index fb397035e6..aebc1d3c46 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -90,5 +90,7 @@ X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::None) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::None) \ + X(int32_t, NotifyOnActivityThreshold, 5) \ + X(int32_t, NotifyOnNextPromptThreshold, 5) \ X(bool, AutoDetectRunningCommand, false) \ X(winrt::hstring, DragDropDelimiter, L" ") From 56718992ac4e3f880e8c0ba0122ece1630927f9d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 12 May 2026 18:21:09 -0700 Subject: [PATCH 22/24] reuse existing resources for auto props --- .../TerminalSettingsEditor/Profiles_Advanced.xaml | 4 ++-- .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 2a106581e8..d2a1c3c5e0 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -224,7 +224,7 @@ ClearSettingValue="{x:Bind Profile.ClearNotifyOnActivityThreshold}" HasSettingValue="{x:Bind Profile.HasNotifyOnActivityThreshold, Mode=OneWay}" SettingOverrideSource="{x:Bind Profile.NotifyOnActivityThresholdOverrideSource, Mode=OneWay}"> - - Suppress repeated activity notifications within this many seconds of the previous one. Set to 0 to always notify. Help text for the minimum-duration cooldown applied to "notify on activity" notifications. - - Minimum seconds between activity notifications - Accessible name for a numeric control that sets the minimum number of seconds between consecutive "notify on activity" notifications. - Notify on next prompt Header for a control to set the notification style when a new shell prompt is detected (requires shell integration). @@ -1613,10 +1609,6 @@ Only notify when the command ran for at least this many seconds. Requires shell integration. Set to 0 to always notify. Help text for the minimum-duration threshold applied to "notify on next prompt" notifications. - - Minimum duration for next prompt notification - Accessible name for a numeric control that sets the minimum number of seconds a command must run before its completion triggers a "notify on next prompt" notification. - Auto-detect running command Header for a control to configure automatic detection of a running command's progress state. From 89b2295a8cd98daa47172d67ed3fd5c46f111562 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 12 May 2026 18:34:10 -0700 Subject: [PATCH 23/24] code format --- src/cascadia/TerminalSettingsModel/MTSMSettings.h | 4 ++-- src/cascadia/inc/ControlProperties.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index b9127f93ec..fb583f24a9 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -112,8 +112,8 @@ Author(s): X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, "notifyOnActivity", Microsoft::Terminal::Control::OutputNotificationStyle::None) \ X(Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, "notifyOnNextPrompt", Microsoft::Terminal::Control::OutputNotificationStyle::None) \ - X(int32_t, NotifyOnActivityThreshold, "notifyOnActivityThreshold", 5) \ - X(int32_t, NotifyOnNextPromptThreshold, "notifyOnNextPromptThreshold", 5) \ + X(int32_t, NotifyOnActivityThreshold, "notifyOnActivityThreshold", 5) \ + X(int32_t, NotifyOnNextPromptThreshold, "notifyOnNextPromptThreshold", 5) \ X(bool, AutoDetectRunningCommand, "autoDetectRunningCommand", false) // Intentionally omitted Profile settings: diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index aebc1d3c46..5b24ea1889 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -90,7 +90,7 @@ X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnActivity, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::None) \ X(winrt::Microsoft::Terminal::Control::OutputNotificationStyle, NotifyOnNextPrompt, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::None) \ - X(int32_t, NotifyOnActivityThreshold, 5) \ - X(int32_t, NotifyOnNextPromptThreshold, 5) \ + X(int32_t, NotifyOnActivityThreshold, 5) \ + X(int32_t, NotifyOnNextPromptThreshold, 5) \ X(bool, AutoDetectRunningCommand, false) \ X(winrt::hstring, DragDropDelimiter, L" ") From e42b3020b04f5d3755f44e5fc0b08b4dba1d664d Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 13 May 2026 11:25:21 -0700 Subject: [PATCH 24/24] remove '-' & use 'threshold' term --- .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index e0d8a1d03e..65eadf509e 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -1591,7 +1591,7 @@ Suppress repeated activity notifications within this many seconds of the previous one. Set to 0 to always notify. - Help text for the minimum-duration cooldown applied to "notify on activity" notifications. + Help text for the minimum duration threshold applied to "notify on activity" notifications. Notify on next prompt @@ -1607,7 +1607,7 @@ Only notify when the command ran for at least this many seconds. Requires shell integration. Set to 0 to always notify. - Help text for the minimum-duration threshold applied to "notify on next prompt" notifications. + Help text for the minimum duration threshold applied to "notify on next prompt" notifications. Auto-detect running command