Compare commits

...

12 Commits

Author SHA1 Message Date
Mike Griese
70eeaca38a The whole thing 2026-03-04 07:59:08 -06:00
Mike Griese
6e34294695 starting 2026-03-03 14:29:47 -06:00
Mike Griese
e570246bfc send notifications even when we're unpackaged 2026-03-03 13:37:20 -06:00
Mike Griese
a0adf411a7 I think this is the whole thing 2026-03-03 12:54:58 -06:00
Mike Griese
20dcfd098b better names 2026-03-03 06:39:58 -06:00
Mike Griese
267d379747 cleaner notification text 2026-03-03 05:43:55 -06:00
Mike Griese
8a132d6b77 cleaner 2026-03-03 05:18:22 -06:00
Mike Griese
d449c86a33 don't pling 900 times on startup 2026-03-02 14:03:42 -06:00
Mike Griese
cd8ff95c1a notify when we automark progress 2026-03-02 13:44:22 -06:00
Mike Griese
19cfe09bda fuck man 2026-03-02 10:10:10 -06:00
Mike Griese
e8d1933b9e jesus what 2026-03-02 05:53:01 -06:00
Dustin L. Howett
83d0d9df14 version: bump to 1.26 on main (#19915)
Signed-off-by: Dustin L. Howett <dustin@howett.net>
2026-02-27 17:52:43 -06:00
56 changed files with 1554 additions and 76 deletions

View File

@@ -3,9 +3,9 @@
<!-- This file is read by XES, which we use in our Release builds. -->
<PropertyGroup Label="Version">
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2026</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>25</VersionMinor>
<VersionMinor>26</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
<VersionInfoCulture>1033</VersionInfoCulture>
<!-- The default has a spacing problem -->

View File

@@ -60,7 +60,8 @@
"enum": [
"audible",
"window",
"taskbar"
"taskbar",
"notification"
]
}
},
@@ -70,6 +71,37 @@
"audible",
"taskbar",
"window",
"notification",
"all",
"none"
]
}
]
},
"OutputNotificationStyle": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"taskbar",
"audible",
"tab",
"notification"
]
}
},
{
"type": "string",
"enum": [
"taskbar",
"audible",
"tab",
"notification",
"all",
"none"
]
@@ -94,6 +126,14 @@
}
]
},
"AutoDetectRunningCommand": {
"type": "string",
"enum": [
"disabled",
"automatic",
"progress"
]
},
"BuiltinSuggestionSource": {
"type": "string",
"anyOf": [
@@ -3174,6 +3214,21 @@
"mingw"
],
"type": "string"
},
"notifyOnInactiveOutput": {
"default": "none",
"description": "Controls how the terminal notifies you when a background pane produces output. Supported values include `taskbar`, `audible`, `tab`, and `notification`. Can be set to true (equivalent to `tab`), false/none (disabled), a single string, or an array of strings.",
"$ref": "#/$defs/OutputNotificationStyle"
},
"notifyOnNextPrompt": {
"default": "none",
"description": "Controls how the terminal notifies you when a running command finishes and the shell returns to a prompt. Requires shell integration. Supported values include `taskbar`, `audible`, `tab`, and `notification`. Can be set to true (equivalent to `tab`), false/none (disabled), a single string, or an array of strings.",
"$ref": "#/$defs/OutputNotificationStyle"
},
"autoDetectRunningCommand": {
"default": "disabled",
"description": "Controls automatic detection of running commands via shell integration marks. When set to `automatic`, an indeterminate progress indicator is shown on the tab while a command is executing. When set to `progress`, the terminal will also attempt to detect progress percentages from command output. Requires shell integration.",
"$ref": "#/$defs/AutoDetectRunningCommand"
}
}
},

View File

@@ -158,7 +158,7 @@ namespace winrt::TerminalApp::implementation
const auto& realArgs = args.ActionArgs().try_as<NextTabArgs>();
if (realArgs)
{
_SelectNextTab(true, realArgs.SwitcherMode());
_SelectNextTab(true, realArgs.SwitcherMode(), realArgs.Filter());
args.Handled(true);
}
}
@@ -169,7 +169,7 @@ namespace winrt::TerminalApp::implementation
const auto& realArgs = args.ActionArgs().try_as<PrevTabArgs>();
if (realArgs)
{
_SelectNextTab(false, realArgs.SwitcherMode());
_SelectNextTab(false, realArgs.SwitcherMode(), realArgs.Filter());
args.Handled(true);
}
}

View File

@@ -397,8 +397,8 @@ void AppCommandlineArgs::_buildFocusTabParser()
// GH#10070 - make sure to not use the MRU order when switching
// tabs on the commandline. That wouldn't make any sense!
focusTabAction.Args(_focusNextTab ?
static_cast<IActionArgs>(NextTabArgs(TabSwitcherMode::Disabled)) :
static_cast<IActionArgs>(PrevTabArgs(TabSwitcherMode::Disabled)));
static_cast<IActionArgs>(NextTabArgs(TabSwitcherMode::Disabled, TabStatusFilter::All)) :
static_cast<IActionArgs>(PrevTabArgs(TabSwitcherMode::Disabled, TabStatusFilter::All)));
_startupActions.push_back(std::move(focusTabAction));
}
});

View File

@@ -10,6 +10,7 @@ namespace winrt::TerminalApp::implementation
til::typed_event<> ConnectionStateChanged;
til::typed_event<IPaneContent> CloseRequested;
til::typed_event<IPaneContent, winrt::TerminalApp::BellEventArgs> BellRequested;
til::typed_event<IPaneContent, winrt::TerminalApp::NotificationEventArgs> NotificationRequested;
til::typed_event<IPaneContent> TitleChanged;
til::typed_event<IPaneContent> TabColorChanged;
til::typed_event<IPaneContent> TaskbarProgressChanged;

View File

@@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "DesktopNotification.h"
#include <WtExeUtils.h>
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;
namespace winrt::TerminalApp::implementation
{
std::atomic<int64_t> DesktopNotification::_lastNotificationTime{ 0 };
bool DesktopNotification::ShouldSendNotification()
{
FILETIME ft{};
GetSystemTimeAsFileTime(&ft);
const auto now = (static_cast<int64_t>(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.
_lastNotificationTime.compare_exchange_strong(last, now, std::memory_order_relaxed);
return true;
}
void DesktopNotification::SendNotification(
const DesktopNotificationArgs& args,
std::function<void(uint32_t tabIndex)> activated)
{
try
{
if (!ShouldSendNotification())
{
return;
}
// Build the toast XML. We use a simple template with a title and body text.
//
// <toast launch="__fromToast">
// <visual>
// <binding template="ToastGeneric">
// <text>Title</text>
// <text>Message</text>
// </binding>
// </visual>
// </toast>
auto toastXml = ToastNotificationManager::GetTemplateContent(ToastTemplateType::ToastText02);
auto textNodes = toastXml.GetElementsByTagName(L"text");
// First <text> is the title
textNodes.Item(0).InnerText(args.Title);
// Second <text> 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");
// Set the scenario to "reminder" to ensure the toast shows even in DND,
// and the group/tag to allow replacement of repeated notifications.
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 (activated)
{
const auto tabIndex = args.TabIndex;
toast.Activated([activated, tabIndex](const auto& /*sender*/, const auto& /*eventArgs*/) {
activated(tabIndex);
});
}
// For packaged apps, CreateToastNotifier() uses the package identity automatically.
// For unpackaged apps, we must pass the explicit AUMID that was registered
// at startup via SetCurrentProcessExplicitAppUserModelID.
auto notifier = IsPackaged()
? ToastNotificationManager::CreateToastNotifier()
: ToastNotificationManager::CreateToastNotifier(
#if defined(WT_BRANDING_RELEASE)
L"Microsoft.WindowsTerminal"
#elif defined(WT_BRANDING_PREVIEW)
L"Microsoft.WindowsTerminalPreview"
#elif defined(WT_BRANDING_CANARY)
L"Microsoft.WindowsTerminalCanary"
#else
L"Microsoft.WindowsTerminalDev"
#endif
);
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();
}
}
}

View File

@@ -0,0 +1,52 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- DesktopNotification.h
Module Description:
- Helper for sending Windows desktop toast notifications. Used by the
`OutputNotificationStyle::Notification` flag to surface activity
and prompt-return 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:
// 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.
//
// activated: A callback invoked on the background thread when the
// toast is clicked. The uint32_t parameter is the tab index.
static void SendNotification(
const DesktopNotificationArgs& args,
std::function<void(uint32_t tabIndex)> activated);
// Rate-limits toast notifications so we don't spam the user.
// Returns true if a notification is allowed, false if too recent.
static bool ShouldSendNotification();
private:
static std::atomic<int64_t> _lastNotificationTime;
// Minimum interval between notifications, in 100ns ticks (FILETIME units).
// 5 seconds = 5 * 10,000,000
static constexpr int64_t MinNotificationIntervalTicks = 50'000'000LL;
};
}

View File

@@ -14,6 +14,13 @@ namespace TerminalApp
runtimeclass BellEventArgs
{
Boolean FlashTaskbar { get; };
Boolean SendNotification { get; };
};
runtimeclass NotificationEventArgs
{
Microsoft.Terminal.Control.OutputNotificationStyle Style { get; };
Boolean OnlyWhenInactive { get; };
};
interface IPaneContent
@@ -41,6 +48,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
event Windows.Foundation.TypedEventHandler<IPaneContent, BellEventArgs> BellRequested;
event Windows.Foundation.TypedEventHandler<IPaneContent, NotificationEventArgs> NotificationRequested;
event Windows.Foundation.TypedEventHandler<IPaneContent, Object> TitleChanged;
event Windows.Foundation.TypedEventHandler<IPaneContent, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<IPaneContent, Object> TaskbarProgressChanged;

View File

@@ -923,4 +923,16 @@
<data name="InvalidRegex" xml:space="preserve">
<value>An invalid regular expression was found.</value>
</data>
<data name="NotificationTitle" xml:space="preserve">
<value>Windows Terminal</value>
<comment>Title shown in desktop toast notifications for tab activity.</comment>
</data>
<data name="NotificationMessage_TabActivity" xml:space="preserve">
<value>Tab "{0}" has new activity</value>
<comment>{Locked="{0}"}Message shown in a desktop toast notification when a tab produces output. {0} is the tab title.</comment>
</data>
<data name="NotificationMessage_TabActivityInWindow" xml:space="preserve">
<value>Tab "{0}" in {1} has new activity</value>
<comment>{Locked="{0}"}{Locked="{1}"}Message shown in a desktop toast notification when a tab produces output. {0} is the tab title. {1} is the window name.</comment>
</data>
</root>

View File

@@ -123,6 +123,15 @@ namespace winrt::TerminalApp::implementation
_bellIndicatorTimer.Stop();
}
// Method Description:
// - Called when the timer for the activity indicator in the tab header fires
// - Removes the activity indicator from the tab header
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 +338,11 @@ namespace winrt::TerminalApp::implementation
{
ShowBellIndicator(false);
}
// When we gain focus, remove the activity indicator if it is active
if (_tabStatus.ActivityIndicator())
{
ShowActivityIndicator(false);
}
}
}
@@ -459,6 +473,29 @@ namespace winrt::TerminalApp::implementation
_bellIndicatorTimer.Start();
}
void Tab::ShowActivityIndicator(const bool show)
{
ASSERT_UI_THREAD();
_tabStatus.ActivityIndicator(show);
}
// Method Description:
// - Activates the timer for the activity indicator in the tab
// - Called if a notification was raised when the tab already has focus
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 +1105,7 @@ namespace winrt::TerminalApp::implementation
dispatcher,
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 200 },
.leading = true,
.trailing = true,
},
[weakThis]() {
@@ -1148,6 +1186,14 @@ namespace winrt::TerminalApp::implementation
tab->TabRaiseVisualBell.raise();
}
// Send a desktop toast notification if requested, but only if
// the pane isn't already in the belled state. This prevents
// spamming toasts for repeated BEL characters.
if (bellArgs.SendNotification() && !tab->_tabStatus.BellIndicator())
{
tab->TabToastNotificationRequested.raise(tab->Title(), tab->TabViewIndex());
}
// Show the bell indicator in the tab header
tab->ShowBellIndicator(true);
@@ -1161,6 +1207,63 @@ namespace winrt::TerminalApp::implementation
}
});
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() })
{
// For NotifyOnInactiveOutput (OnlyWhenInactive=true), suppress
// ALL notification styles when the sender is the active pane.
// For NotifyOnNextPrompt (OnlyWhenInactive=false), always fire.
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, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::Taskbar))
{
// Flash the taskbar button
tab->TabRaiseVisualBell.raise();
}
if (WI_IsFlagSet(style, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::Audible))
{
// Play the notification sound via the pane's bell infrastructure
// (respects BellSound profile setting, uses MediaPlayer, etc.)
if (const auto termContent{ sender.try_as<TerminalApp::TerminalPaneContent>() })
{
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))
{
// Request a desktop toast notification.
// TerminalPage subscribes to this event and handles sending the toast
// and processing its activation (summoning the window + switching tabs).
tab->TabToastNotificationRequested.raise(tab->Title(), tab->TabViewIndex());
}
}
});
if (const auto& terminal{ content.try_as<TerminalApp::TerminalPaneContent>() })
{
events.RestartTerminalRequested = terminal.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &Tab::_bubbleRestartTerminalRequested });
@@ -1202,6 +1305,62 @@ namespace winrt::TerminalApp::implementation
*std::min_element(states.begin(), states.end(), TerminalApp::implementation::TaskbarState::ComparePriority);
}
// Method Description:
// - Returns true if this tab matches the given TabStatusFilter flags.
// A tab matches if ANY of the requested status flags are active (OR semantic).
// If filter is All (0xffffffff), always returns true.
// Arguments:
// - filter: A combination of TabStatusFilter flags to check.
// Return Value:
// - true if the tab matches the filter, false otherwise.
bool Tab::MatchesFilter(const winrt::Microsoft::Terminal::Settings::Model::TabStatusFilter filter) const
{
ASSERT_UI_THREAD();
using TSF = winrt::Microsoft::Terminal::Settings::Model::TabStatusFilter;
// All means no filtering — every tab matches.
if (filter == TSF::All)
{
return true;
}
// Check bell indicator
if (WI_IsFlagSet(filter, TSF::Bell) && _tabStatus.BellIndicator())
{
return true;
}
// Check activity indicator
if (WI_IsFlagSet(filter, TSF::Activity) && _tabStatus.ActivityIndicator())
{
return true;
}
// Check progress-related flags against the combined taskbar state.
// TaskbarState::State() values from DispatchTypes.hpp:
// Clear = 0, Set = 1, Error = 2, Indeterminate = 3, Paused = 4
const auto taskbarState = GetCombinedTaskbarState().State();
if (WI_IsFlagSet(filter, TSF::Set) && taskbarState == 1)
{
return true;
}
if (WI_IsFlagSet(filter, TSF::Error) && taskbarState == 2)
{
return true;
}
if (WI_IsFlagSet(filter, TSF::Indeterminate) && taskbarState == 3)
{
return true;
}
if (WI_IsFlagSet(filter, TSF::Paused) && taskbarState == 4)
{
return true;
}
return false;
}
// Method Description:
// - This should be called on the UI thread. If you don't, then it might
// silently do nothing.
@@ -1393,6 +1552,11 @@ namespace winrt::TerminalApp::implementation
{
tab->ShowBellIndicator(false);
}
// Also remove the activity indicator
if (tab->_tabStatus.ActivityIndicator())
{
tab->ShowActivityIndicator(false);
}
}
});

View File

@@ -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<winrt::Microsoft::Terminal::Settings::Model::SplitDirection> PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
@@ -90,6 +93,7 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Pane> GetActivePane() const;
winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const;
bool MatchesFilter(const winrt::Microsoft::Terminal::Settings::Model::TabStatusFilter filter) const;
std::shared_ptr<Pane> GetRootPane() const { return _rootPane; }
std::vector<uint32_t> GetMruPanes() const { return _mruPanes; }
@@ -121,6 +125,7 @@ namespace winrt::TerminalApp::implementation
til::typed_event<TerminalApp::Tab, IInspectable> ActivePaneChanged;
til::event<winrt::delegate<>> TabRaiseVisualBell;
til::event<winrt::delegate<winrt::hstring /*title*/, uint32_t /*tabIndex*/>> TabToastNotificationRequested;
til::typed_event<IInspectable, IInspectable> TaskbarProgressChanged;
// The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector.
@@ -176,6 +181,7 @@ namespace winrt::TerminalApp::implementation
struct ContentEventTokens
{
winrt::TerminalApp::IPaneContent::BellRequested_revoker BellRequested;
winrt::TerminalApp::IPaneContent::NotificationRequested_revoker NotificationRequested;
winrt::TerminalApp::IPaneContent::TitleChanged_revoker TitleChanged;
winrt::TerminalApp::IPaneContent::TabColorChanged_revoker TabColorChanged;
winrt::TerminalApp::IPaneContent::TaskbarProgressChanged_revoker TaskbarProgressChanged;
@@ -210,6 +216,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();

View File

@@ -32,6 +32,12 @@
FontSize="12"
Glyph="&#xEA8F;"
Visibility="{x:Bind TabStatus.BellIndicator, Mode=OneWay}" />
<FontIcon x:Name="HeaderActivityIndicator"
Margin="0,0,8,0"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="8"
Glyph="&#xF127;"
Visibility="{x:Bind TabStatus.ActivityIndicator, Mode=OneWay}" />
<FontIcon x:Name="HeaderZoomIcon"
Margin="0,0,8,0"
FontFamily="{ThemeResource SymbolThemeFontFamily}"

View File

@@ -19,6 +19,7 @@
#include "DebugTapConnection.h"
#include "..\TerminalSettingsModel\FileUtils.h"
#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"
#include "DesktopNotification.h"
#include <shlobj.h>
@@ -150,6 +151,15 @@ namespace winrt::TerminalApp::implementation
}
});
// When a tab requests a desktop toast notification (OutputNotificationStyle::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, uint32_t tabIndex) {
if (const auto page{ weakThis.get() })
{
page->_SendDesktopNotification(title, tabIndex);
}
});
auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().InsertAt(insertPosition, tabViewItem);
@@ -525,30 +535,111 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Sets focus to the tab to the right or left the currently selected tab.
void TerminalPage::_SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode)
// When a filter is specified (not All), only tabs matching the filter are considered.
void TerminalPage::_SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode, const Microsoft::Terminal::Settings::Model::TabStatusFilter filter)
{
const auto index{ _GetFocusedTabIndex().value_or(0) };
const auto tabSwitchMode = customTabSwitcherMode ? customTabSwitcherMode.Value() : _settings.GlobalSettings().TabSwitcherMode();
if (tabSwitchMode == TabSwitcherMode::Disabled)
{
auto tabCount = _tabs.Size();
// Wraparound math. By adding tabCount and then calculating
// modulo tabCount, we clamp the values to the range [0,
// tabCount) while still supporting moving leftward from 0 to
// tabCount - 1.
const auto newTabIndex = ((tabCount + index + (bMoveRight ? 1 : -1)) % tabCount);
_SelectTab(newTabIndex);
const auto tabCount = _tabs.Size();
if (tabCount == 0)
{
return;
}
if (filter == TabStatusFilter::All)
{
// Original unfiltered behavior
const auto newTabIndex = ((tabCount + index + (bMoveRight ? 1 : -1)) % tabCount);
_SelectTab(newTabIndex);
}
else
{
// Iterate in the given direction, wrapping around, looking for a matching tab.
for (uint32_t i = 1; i <= tabCount; i++)
{
const auto candidateIndex = ((tabCount + index + (bMoveRight ? i : -static_cast<int>(i))) % tabCount);
if (const auto tabImpl = _GetTabImpl(_tabs.GetAt(candidateIndex)))
{
if (tabImpl->MatchesFilter(filter))
{
_SelectTab(candidateIndex);
return;
}
}
}
// No match found — stay on current tab.
}
}
else
{
const auto p = LoadCommandPalette();
p.SetTabs(_tabs, _mruTabs);
if (filter == TabStatusFilter::All)
{
// Original unfiltered behavior
const auto p = LoadCommandPalette();
p.SetTabs(_tabs, _mruTabs);
p.EnableTabSwitcherMode(index, tabSwitchMode);
p.Visibility(Visibility::Visible);
p.SelectNextItem(bMoveRight);
}
else
{
// Build filtered copies of _tabs and _mruTabs.
auto filteredTabs = winrt::single_threaded_observable_vector<TerminalApp::Tab>();
auto filteredMruTabs = winrt::single_threaded_observable_vector<TerminalApp::Tab>();
// Otherwise, set up the tab switcher in the selected mode, with
// the given ordering, and make it visible.
p.EnableTabSwitcherMode(index, tabSwitchMode);
p.Visibility(Visibility::Visible);
p.SelectNextItem(bMoveRight);
for (const auto& tab : _tabs)
{
if (const auto tabImpl = _GetTabImpl(tab))
{
if (tabImpl->MatchesFilter(filter))
{
filteredTabs.Append(tab);
}
}
}
// If no tabs match the filter, do nothing.
if (filteredTabs.Size() == 0)
{
return;
}
for (const auto& tab : _mruTabs)
{
if (const auto tabImpl = _GetTabImpl(tab))
{
if (tabImpl->MatchesFilter(filter))
{
filteredMruTabs.Append(tab);
}
}
}
// Find the current tab's position in the filtered list.
const auto currentTab = _tabs.GetAt(index);
uint32_t filteredStartIdx = 0;
if (!filteredTabs.IndexOf(currentTab, filteredStartIdx))
{
// Current tab is not in the filtered set. Position so the
// first SelectNextItem call wraps to the appropriate end.
if (bMoveRight)
{
filteredStartIdx = filteredTabs.Size() - 1;
}
else
{
filteredStartIdx = 0;
}
}
const auto p = LoadCommandPalette();
p.SetTabs(filteredTabs, filteredMruTabs);
p.EnableTabSwitcherMode(filteredStartIdx, tabSwitchMode);
p.Visibility(Visibility::Visible);
p.SelectNextItem(bMoveRight);
}
}
}
@@ -1185,4 +1276,56 @@ namespace winrt::TerminalApp::implementation
{
return _tabs.Size() > 1;
}
// Method Description:
// - Sends a Windows desktop toast notification for a tab. When the user clicks
// the toast, summon this window and switch to the specified tab.
// Arguments:
// - tabTitle: The title of the tab to display in the notification.
// - tabIndex: The index of the tab to switch to when the toast is activated.
void TerminalPage::_SendDesktopNotification(const winrt::hstring& tabTitle, uint32_t tabIndex)
{
// Build the notification message.
// 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 "<unnamed window>" placeholder in the notification body.
const auto windowName = _WindowProperties ? _WindowProperties.WindowName() : winrt::hstring{};
winrt::hstring message;
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 });
}
implementation::DesktopNotificationArgs args;
args.Title = RS_(L"NotificationTitle");
args.Message = message;
args.TabIndex = tabIndex;
// Capture a weak ref and the dispatcher so we can marshal back to the UI thread
// when the toast is activated.
auto weakThis = get_weak();
auto dispatcher = Dispatcher();
implementation::DesktopNotification::SendNotification(
args,
[weakThis, dispatcher, tabIndex](uint32_t /*activatedTabIndex*/) -> void {
// The toast Activated callback fires on a background thread.
// We need to dispatch to the UI thread to summon the window and switch tabs.
[](auto weakThis, auto dispatcher, auto tabIndex) -> safe_void_coroutine {
co_await wil::resume_foreground(dispatcher);
if (const auto page{ weakThis.get() })
{
// Summon this window (bring to foreground)
page->SummonWindowRequested.raise(nullptr, nullptr);
// Switch to the tab that triggered the notification
page->_SelectTab(tabIndex);
}
}(weakThis, dispatcher, tabIndex);
});
}
}

View File

@@ -173,6 +173,7 @@
<ClInclude Include="SettingsPaneContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="DesktopNotification.h" />
<ClInclude Include="Toast.h" />
<ClInclude Include="TerminalSettingsCache.h" />
<ClInclude Include="SuggestionsControl.h">
@@ -286,6 +287,7 @@
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="DesktopNotification.cpp" />
<ClCompile Include="Toast.cpp" />
<ClCompile Include="TerminalSettingsCache.cpp" />
<ClCompile Include="SuggestionsControl.cpp">

View File

@@ -30,6 +30,7 @@
<ClCompile Include="fzf/fzf.cpp">
<Filter>fzf</Filter>
</ClCompile>
<ClCompile Include="DesktopNotification.cpp" />
<ClCompile Include="Toast.cpp" />
<ClCompile Include="LanguageProfileNotifier.cpp" />
<ClCompile Include="TerminalSettingsCache.cpp" />
@@ -58,6 +59,7 @@
<ClInclude Include="fzf/fzf.h">
<Filter>fzf</Filter>
</ClInclude>
<ClInclude Include="DesktopNotification.h" />
<ClInclude Include="Toast.h" />
<ClInclude Include="LanguageProfileNotifier.h" />
<ClInclude Include="WindowsPackageManagerFactory.h" />

View File

@@ -362,7 +362,7 @@ namespace winrt::TerminalApp::implementation
void _FocusCurrentTab(const bool focusAlways);
bool _HasMultipleTabs() const;
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode, const Microsoft::Terminal::Settings::Model::TabStatusFilter filter = Microsoft::Terminal::Settings::Model::TabStatusFilter::All);
bool _SelectTab(uint32_t tabIndex);
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
@@ -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, uint32_t tabIndex);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);

View File

@@ -10,6 +10,7 @@
#include "../../types/inc/utils.hpp"
#include "BellEventArgs.g.cpp"
#include "NotificationEventArgs.g.cpp"
#include "TerminalPaneContent.g.cpp"
using namespace winrt::Windows::Foundation;
@@ -34,8 +35,11 @@ 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 });
_controlEvents._OutputIdle = _control.OutputIdle(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlOutputIdleHandler });
_controlEvents._TitleChanged = _control.TitleChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTitleChanged });
_controlEvents._TabColorChanged = _control.TabColorChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTabColorChanged });
@@ -261,6 +265,30 @@ namespace winrt::TerminalApp::implementation
// has the 'visual' flag set
// Arguments:
// - <unused>
// Method Description:
// - Plays the notification sound using the profile's BellSound setting if
// configured, otherwise falls back to the system "Critical Stop" sound.
// Reused by the warning bell handler, NotifyOnNextPrompt, and
// NotifyOnInactiveOutput (called from Tab after the active-pane check).
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<LPCTSTR>(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*/)
{
@@ -271,19 +299,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<LPCTSTR>(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))
@@ -291,9 +307,112 @@ namespace winrt::TerminalApp::implementation
_control.BellLightOn();
}
// raise the event with the bool value corresponding to the taskbar flag
// raise the event with the bool values corresponding to the taskbar and notification flags
BellRequested.raise(*this,
*winrt::make_self<TerminalApp::implementation::BellEventArgs>(WI_IsFlagSet(_profile.BellStyle(), BellStyle::Taskbar)));
*winrt::make_self<TerminalApp::implementation::BellEventArgs>(
WI_IsFlagSet(_profile.BellStyle(), BellStyle::Taskbar),
WI_IsFlagSet(_profile.BellStyle(), BellStyle::Notification)));
}
}
}
// Method Description:
// - Returns the taskbar state, accounting for auto-detected running command progress.
// VT-set progress (OSC 9;4) takes precedence over auto-detected progress.
uint64_t TerminalPaneContent::TaskbarState()
{
const auto vtState = _control.TaskbarState();
// VT-set progress takes precedence over auto-detected progress
if (vtState != 0)
{
return vtState;
}
// Auto-detected indeterminate progress (between command start and prompt return)
if (_autoDetectActive)
{
return 3; // TaskbarState::Indeterminate
}
return 0;
}
// Method Description:
// - Returns the taskbar progress, accounting for auto-detected running command progress.
uint64_t TerminalPaneContent::TaskbarProgress()
{
const auto vtState = _control.TaskbarState();
// VT-set progress takes precedence
if (vtState != 0)
{
return _control.TaskbarProgress();
}
return 0;
}
// Method Description:
// - Raised when a shell integration prompt mark (133;A) is received, indicating
// the command has finished and we're back at a prompt.
// - Checks NotifyOnNextPrompt setting and raises NotificationRequested.
// - If autoDetectRunningCommand is enabled, clears the indeterminate progress ring.
void TerminalPaneContent::_controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
{
if (_profile)
{
// Check NotifyOnNextPrompt setting and raise a notification.
// Pass OnlyWhenInactive=true so Tab suppresses notifications when
// this pane is active in a focused tab.
const auto notifyStyle = _profile.NotifyOnNextPrompt();
if (static_cast<int>(notifyStyle) != 0)
{
NotificationRequested.raise(*this,
*winrt::make_self<TerminalApp::implementation::NotificationEventArgs>(notifyStyle, true));
}
// If autoDetectRunningCommand is enabled, clear the progress ring
const auto autoDetect = _profile.AutoDetectRunningCommand();
if (autoDetect != AutoDetectRunningCommand::Disabled && _autoDetectActive)
{
_autoDetectActive = false;
TaskbarProgressChanged.raise(*this, nullptr);
}
}
}
// Method Description:
// - Raised when a shell integration command output mark (133;C) is received,
// indicating a command has started executing.
// - If autoDetectRunningCommand is enabled, shows an indeterminate progress ring.
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);
}
}
}
// Method Description:
// - Raised when output stops being produced for ~100ms (debounced).
// Used to detect output in inactive panes for NotifyOnInactiveOutput.
// - Raises NotificationRequested so Tab can show activity indicators.
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<int>(notifyStyle) != 0)
{
// Raise NotificationRequested so Tab can handle Taskbar/Tab/Audible flags.
// Pass OnlyWhenInactive=true so Tab skips notifications for the active pane.
// Note: Audible is handled by Tab (not here) so it can be gated on active state.
NotificationRequested.raise(*this,
*winrt::make_self<TerminalApp::implementation::NotificationEventArgs>(notifyStyle, true));
}
}
}

View File

@@ -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
@@ -13,10 +14,21 @@ namespace winrt::TerminalApp::implementation
struct BellEventArgs : public BellEventArgsT<BellEventArgs>
{
public:
BellEventArgs(bool flashTaskbar) :
FlashTaskbar(flashTaskbar) {}
BellEventArgs(bool flashTaskbar, bool sendNotification) :
FlashTaskbar(flashTaskbar), SendNotification(sendNotification) {}
til::property<bool> FlashTaskbar;
til::property<bool> SendNotification;
};
struct NotificationEventArgs : public NotificationEventArgsT<NotificationEventArgs>
{
public:
NotificationEventArgs(winrt::Microsoft::Terminal::Control::OutputNotificationStyle style, bool onlyWhenInactive = false) :
Style(style), OnlyWhenInactive(onlyWhenInactive) {}
til::property<winrt::Microsoft::Terminal::Control::OutputNotificationStyle> Style;
til::property<bool> OnlyWhenInactive;
};
struct TerminalPaneContent : TerminalPaneContentT<TerminalPaneContent>, BasicPaneEvents
@@ -36,6 +48,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
{
@@ -43,8 +56,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<winrt::Windows::UI::Color> TabColor() const noexcept;
@@ -66,11 +79,15 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
bool _bellPlayerCreated{ false };
std::atomic<bool> _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;
@@ -89,6 +106,12 @@ 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& e);
void _controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& e);
void _controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& e);
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);

View File

@@ -11,6 +11,7 @@ namespace TerminalApp
Microsoft.Terminal.Control.TermControl GetTermControl();
void MarkAsDefterm();
void PlayNotificationSound();
Microsoft.Terminal.Settings.Model.Profile GetProfile();

View File

@@ -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);

View File

@@ -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; };

View File

@@ -54,6 +54,9 @@
#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

View File

@@ -118,6 +118,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnWarningBell = [this] { _terminalWarningBell(); };
_terminal->SetWarningBellCallback(pfnWarningBell);
auto pfnPromptStarted = [this] { _terminalPromptStarted(); };
_terminal->SetPromptStartedCallback(pfnPromptStarted);
auto pfnOutputStarted = [this] { _terminalOutputStarted(); };
_terminal->SetOutputStartedCallback(pfnOutputStarted);
auto pfnTitleChanged = [this](auto&& PH1) { _terminalTitleChanged(std::forward<decltype(PH1)>(PH1)); };
_terminal->SetTitleChangedCallback(pfnTitleChanged);
@@ -1590,9 +1596,37 @@ 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()
{
// 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;
}
PromptStarted.raise(*this, nullptr);
}
void ControlCore::_terminalOutputStarted()
{
// 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;
}
OutputStarted.raise(*this, nullptr);
}
// Method Description:
// - Called for the Terminal's TitleChanged callback. This will re-raise
// a new winrt TypedEvent that can be listened to.
@@ -1650,6 +1684,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::_terminalTaskbarProgressChanged()
{
if (_restoring)
{
return;
}
TaskbarProgressChanged.raise(*this, nullptr);
}
@@ -1667,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();
@@ -1839,8 +1881,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SerializeMainBuffer(handle);
}
void ControlCore::RestoreFromPath(const wchar_t* path) const
void ControlCore::RestoreFromPath(const wchar_t* path)
{
// Suppress notifications (bells, prompt-returned, command-started, etc.)
// while we replay persisted buffer content. Without this, restoring a
// session fires the same events that live output would, producing
// unwanted audible bells, tab activity indicators, and taskbar flashes.
_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

View File

@@ -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();
@@ -276,6 +276,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::typed_event<IInspectable, Control::TitleChangedEventArgs> TitleChanged;
til::typed_event<IInspectable, Control::WriteToClipboardEventArgs> WriteToClipboard;
til::typed_event<> WarningBell;
til::typed_event<> PromptStarted;
til::typed_event<> OutputStarted;
til::typed_event<> TabColorChanged;
til::typed_event<> BackgroundColorChanged;
til::typed_event<IInspectable, Control::ScrollPositionChangedArgs> ScrollPositionChanged;
@@ -324,6 +326,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,
@@ -420,6 +424,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::optional<til::point> _lastHoveredCell;
uint16_t _lastHoveredId{ 0 };
std::atomic<bool> _initializedTerminal{ false };
std::atomic<bool> _restoring{ false };
bool _isReadOnly{ false };
bool _closing{ false };

View File

@@ -191,6 +191,8 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, WriteToClipboardEventArgs> WriteToClipboard;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> PromptStarted;
event Windows.Foundation.TypedEventHandler<Object, Object> OutputStarted;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;

View File

@@ -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
@@ -77,6 +94,10 @@ namespace Microsoft.Terminal.Control
PathTranslationStyle PathTranslationStyle { get; };
OutputNotificationStyle NotifyOnInactiveOutput { get; };
OutputNotificationStyle NotifyOnNextPrompt { get; };
AutoDetectRunningCommand AutoDetectRunningCommand { get; };
// NOTE! When adding something here, make sure to update ControlProperties.h too!
};
}

View File

@@ -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<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
@@ -3699,6 +3701,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();
@@ -3820,6 +3832,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)

View File

@@ -212,6 +212,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::typed_event<IInspectable, IInspectable> FocusFollowMouseRequested;
til::typed_event<Control::TermControl, Windows::UI::Xaml::RoutedEventArgs> Initialized;
til::typed_event<> WarningBell;
til::typed_event<> PromptStarted;
til::typed_event<> OutputStarted;
til::typed_event<> OutputIdle;
til::typed_event<IInspectable, Control::KeySentEventArgs> KeySent;
til::typed_event<IInspectable, Control::CharSentEventArgs> CharSent;
til::typed_event<IInspectable, Control::StringSentEventArgs> StringSent;
@@ -425,6 +428,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);
@@ -450,6 +455,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;

View File

@@ -68,6 +68,9 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> PromptStarted;
event Windows.Foundation.TypedEventHandler<Object, Object> OutputStarted;
event Windows.Foundation.TypedEventHandler<Object, Object> OutputIdle;
event Windows.Foundation.TypedEventHandler<Object, Object> HidePointerCursor;
event Windows.Foundation.TypedEventHandler<Object, Object> RestorePointerCursor;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;

View File

@@ -750,6 +750,12 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s
// This changed the scrollbar marks - raise a notification to update them
_NotifyScrollEvent();
}
// regardless, start notify that we started command output
if (_pfnOutputStarted)
{
_pfnOutputStarted();
}
}
}
@@ -1265,6 +1271,16 @@ void Microsoft::Terminal::Core::Terminal::SetClearQuickFixCallback(std::function
_pfnClearQuickFix.swap(pfn);
}
void Terminal::SetPromptStartedCallback(std::function<void()> pfn) noexcept
{
_pfnPromptStarted.swap(pfn);
}
void Terminal::SetOutputStartedCallback(std::function<void()> pfn) noexcept
{
_pfnOutputStarted.swap(pfn);
}
// Method Description:
// - Stores the search highlighted regions in the terminal
void Terminal::SetSearchHighlights(const std::vector<til::point_span>& highlights) noexcept

View File

@@ -156,7 +156,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;
@@ -232,6 +232,8 @@ public:
void SetSearchMissingCommandCallback(std::function<void(std::wstring_view, const til::CoordType)> pfn) noexcept;
void SetClearQuickFixCallback(std::function<void()> pfn) noexcept;
void SetWindowSizeChangedCallback(std::function<void(int32_t, int32_t)> pfn) noexcept;
void SetPromptStartedCallback(std::function<void()> pfn) noexcept;
void SetOutputStartedCallback(std::function<void()> pfn) noexcept;
void SetSearchHighlights(const std::vector<til::point_span>& highlights) noexcept;
void SetSearchHighlightFocused(size_t focusedIdx) noexcept;
void ScrollToSearchHighlight(til::CoordType searchScrollOffset);
@@ -340,6 +342,8 @@ private:
std::function<void(std::wstring_view, const til::CoordType)> _pfnSearchMissingCommand;
std::function<void()> _pfnClearQuickFix;
std::function<void(int32_t, int32_t)> _pfnWindowSizeChanged;
std::function<void()> _pfnPromptStarted;
std::function<void()> _pfnOutputStarted;
RenderSettings _renderSettings;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;

View File

@@ -400,8 +400,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;
}
}

View File

@@ -353,6 +353,10 @@ namespace winrt::Microsoft::Terminal::Settings
_AllowVtChecksumReport = profile.AllowVtChecksumReport();
_AllowVtClipboardWrite = profile.AllowVtClipboardWrite();
_PathTranslationStyle = profile.PathTranslationStyle();
_NotifyOnInactiveOutput = profile.NotifyOnInactiveOutput();
_NotifyOnNextPrompt = profile.NotifyOnNextPrompt();
_AutoDetectRunningCommand = profile.AutoDetectRunningCommand();
}
// Method Description:

View File

@@ -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();
@@ -574,7 +575,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
hstring ProfileViewModel::BellStylePreview() const
{
const auto bellStyle = BellStyle();
if (WI_AreAllFlagsSet(bellStyle, BellStyle::Audible | BellStyle::Window | BellStyle::Taskbar))
if (WI_AreAllFlagsSet(bellStyle, BellStyle::Audible | BellStyle::Window | BellStyle::Taskbar | BellStyle::Notification))
{
return RS_(L"Profile_BellStyleAll/Content");
}
@@ -584,7 +585,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
std::vector<hstring> resultList;
resultList.reserve(3);
resultList.reserve(4);
if (WI_IsFlagSet(bellStyle, BellStyle::Audible))
{
resultList.emplace_back(RS_(L"Profile_BellStyleAudible/Content"));
@@ -597,6 +598,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
resultList.emplace_back(RS_(L"Profile_BellStyleTaskbar/Content"));
}
if (WI_IsFlagSet(bellStyle, BellStyle::Notification))
{
resultList.emplace_back(RS_(L"Profile_BellStyleNotification/Content"));
}
// add in the commas
hstring result{};
@@ -640,6 +645,177 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
BellStyle(currentStyle);
}
void ProfileViewModel::SetBellStyleNotification(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = BellStyle();
WI_UpdateFlag(currentStyle, Model::BellStyle::Notification, winrt::unbox_value<bool>(on));
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<Ons>(0))
{
return RS_(L"Profile_OutputNotificationStyleNone/Content");
}
std::vector<hstring> 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<bool> on)
{
auto currentStyle = NotifyOnInactiveOutput();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Taskbar, winrt::unbox_value<bool>(on));
NotifyOnInactiveOutput(currentStyle);
}
void ProfileViewModel::SetNotifyOnInactiveOutputAudible(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = NotifyOnInactiveOutput();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Audible, winrt::unbox_value<bool>(on));
NotifyOnInactiveOutput(currentStyle);
}
void ProfileViewModel::SetNotifyOnInactiveOutputTab(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = NotifyOnInactiveOutput();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Tab, winrt::unbox_value<bool>(on));
NotifyOnInactiveOutput(currentStyle);
}
void ProfileViewModel::SetNotifyOnInactiveOutputNotification(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = NotifyOnInactiveOutput();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Notification, winrt::unbox_value<bool>(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<Ons>(0))
{
return RS_(L"Profile_OutputNotificationStyleNone/Content");
}
std::vector<hstring> 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<bool> on)
{
auto currentStyle = NotifyOnNextPrompt();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Taskbar, winrt::unbox_value<bool>(on));
NotifyOnNextPrompt(currentStyle);
}
void ProfileViewModel::SetNotifyOnNextPromptAudible(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = NotifyOnNextPrompt();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Audible, winrt::unbox_value<bool>(on));
NotifyOnNextPrompt(currentStyle);
}
void ProfileViewModel::SetNotifyOnNextPromptTab(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = NotifyOnNextPrompt();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Tab, winrt::unbox_value<bool>(on));
NotifyOnNextPrompt(currentStyle);
}
void ProfileViewModel::SetNotifyOnNextPromptNotification(winrt::Windows::Foundation::IReference<bool> on)
{
auto currentStyle = NotifyOnNextPrompt();
WI_UpdateFlag(currentStyle, Control::OutputNotificationStyle::Notification, winrt::unbox_value<bool>(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

View File

@@ -46,6 +46,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void SetBellStyleAudible(winrt::Windows::Foundation::IReference<bool> on);
void SetBellStyleWindow(winrt::Windows::Foundation::IReference<bool> on);
void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference<bool> on);
void SetBellStyleNotification(winrt::Windows::Foundation::IReference<bool> on);
// notify on inactive output bits
hstring NotifyOnInactiveOutputPreview() const;
bool IsNotifyOnInactiveOutputFlagSet(const uint32_t flag);
void SetNotifyOnInactiveOutputTaskbar(winrt::Windows::Foundation::IReference<bool> on);
void SetNotifyOnInactiveOutputAudible(winrt::Windows::Foundation::IReference<bool> on);
void SetNotifyOnInactiveOutputTab(winrt::Windows::Foundation::IReference<bool> on);
void SetNotifyOnInactiveOutputNotification(winrt::Windows::Foundation::IReference<bool> on);
// notify on next prompt bits
hstring NotifyOnNextPromptPreview() const;
bool IsNotifyOnNextPromptFlagSet(const uint32_t flag);
void SetNotifyOnNextPromptTaskbar(winrt::Windows::Foundation::IReference<bool> on);
void SetNotifyOnNextPromptAudible(winrt::Windows::Foundation::IReference<bool> on);
void SetNotifyOnNextPromptTab(winrt::Windows::Foundation::IReference<bool> on);
void SetNotifyOnNextPromptNotification(winrt::Windows::Foundation::IReference<bool> on);
hstring BellSoundPreview();
void RequestAddBellSound(hstring path);
@@ -146,12 +163,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
OBSERVABLE_PROJECTED_SETTING(_profile, RainbowSuggestions);
OBSERVABLE_PROJECTED_SETTING(_profile, PathTranslationStyle);
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);
GETSET_BINDABLE_ENUM_SETTING(AntiAliasingMode, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode);
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;

View File

@@ -49,6 +49,24 @@ namespace Microsoft.Terminal.Settings.Editor
void SetBellStyleAudible(Windows.Foundation.IReference<Boolean> on);
void SetBellStyleWindow(Windows.Foundation.IReference<Boolean> on);
void SetBellStyleTaskbar(Windows.Foundation.IReference<Boolean> on);
void SetBellStyleNotification(Windows.Foundation.IReference<Boolean> on);
String NotifyOnInactiveOutputPreview { get; };
Boolean IsNotifyOnInactiveOutputFlagSet(UInt32 flag);
void SetNotifyOnInactiveOutputTaskbar(Windows.Foundation.IReference<Boolean> on);
void SetNotifyOnInactiveOutputAudible(Windows.Foundation.IReference<Boolean> on);
void SetNotifyOnInactiveOutputTab(Windows.Foundation.IReference<Boolean> on);
void SetNotifyOnInactiveOutputNotification(Windows.Foundation.IReference<Boolean> on);
String NotifyOnNextPromptPreview { get; };
Boolean IsNotifyOnNextPromptFlagSet(UInt32 flag);
void SetNotifyOnNextPromptTaskbar(Windows.Foundation.IReference<Boolean> on);
void SetNotifyOnNextPromptAudible(Windows.Foundation.IReference<Boolean> on);
void SetNotifyOnNextPromptTab(Windows.Foundation.IReference<Boolean> on);
void SetNotifyOnNextPromptNotification(Windows.Foundation.IReference<Boolean> on);
IInspectable CurrentAutoDetectRunningCommand;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> AutoDetectRunningCommandList { get; };
String BellSoundPreview { get; };
Windows.Foundation.Collections.IObservableVector<BellSoundViewModel> CurrentBellSounds { get; };
@@ -140,5 +158,8 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RainbowSuggestions);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle);
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);
}
}

View File

@@ -111,6 +111,8 @@
IsChecked="{x:Bind Profile.IsBellStyleFlagSet(2), BindBack=Profile.SetBellStyleWindow, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_BellStyleTaskbar"
IsChecked="{x:Bind Profile.IsBellStyleFlagSet(4), BindBack=Profile.SetBellStyleTaskbar, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_BellStyleNotification"
IsChecked="{x:Bind Profile.IsBellStyleFlagSet(8), BindBack=Profile.SetBellStyleNotification, Mode=TwoWay}" />
</StackPanel>
</local:SettingContainer>
@@ -196,6 +198,59 @@
</StackPanel>
</local:SettingContainer>
<!-- Notify On Inactive Output -->
<local:SettingContainer x:Name="NotifyOnInactiveOutput"
x:Uid="Profile_NotifyOnInactiveOutput"
ClearSettingValue="{x:Bind Profile.ClearNotifyOnInactiveOutput}"
CurrentValue="{x:Bind Profile.NotifyOnInactiveOutputPreview, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasNotifyOnInactiveOutput, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.NotifyOnInactiveOutputOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<StackPanel>
<CheckBox x:Uid="Profile_OutputNotificationStyleTaskbar"
IsChecked="{x:Bind Profile.IsNotifyOnInactiveOutputFlagSet(1), BindBack=Profile.SetNotifyOnInactiveOutputTaskbar, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_OutputNotificationStyleAudible"
IsChecked="{x:Bind Profile.IsNotifyOnInactiveOutputFlagSet(2), BindBack=Profile.SetNotifyOnInactiveOutputAudible, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_OutputNotificationStyleTab"
IsChecked="{x:Bind Profile.IsNotifyOnInactiveOutputFlagSet(4), BindBack=Profile.SetNotifyOnInactiveOutputTab, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_OutputNotificationStyleNotification"
IsChecked="{x:Bind Profile.IsNotifyOnInactiveOutputFlagSet(8), BindBack=Profile.SetNotifyOnInactiveOutputNotification, Mode=TwoWay}" />
</StackPanel>
</local:SettingContainer>
<!-- Notify On Next Prompt -->
<local:SettingContainer x:Name="NotifyOnNextPrompt"
x:Uid="Profile_NotifyOnNextPrompt"
ClearSettingValue="{x:Bind Profile.ClearNotifyOnNextPrompt}"
CurrentValue="{x:Bind Profile.NotifyOnNextPromptPreview, Mode=OneWay}"
HasSettingValue="{x:Bind Profile.HasNotifyOnNextPrompt, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.NotifyOnNextPromptOverrideSource, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<StackPanel>
<CheckBox x:Uid="Profile_OutputNotificationStyleTaskbar"
IsChecked="{x:Bind Profile.IsNotifyOnNextPromptFlagSet(1), BindBack=Profile.SetNotifyOnNextPromptTaskbar, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_OutputNotificationStyleAudible"
IsChecked="{x:Bind Profile.IsNotifyOnNextPromptFlagSet(2), BindBack=Profile.SetNotifyOnNextPromptAudible, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_OutputNotificationStyleTab"
IsChecked="{x:Bind Profile.IsNotifyOnNextPromptFlagSet(4), BindBack=Profile.SetNotifyOnNextPromptTab, Mode=TwoWay}" />
<CheckBox x:Uid="Profile_OutputNotificationStyleNotification"
IsChecked="{x:Bind Profile.IsNotifyOnNextPromptFlagSet(8), BindBack=Profile.SetNotifyOnNextPromptNotification, Mode=TwoWay}" />
</StackPanel>
</local:SettingContainer>
<!-- Auto Detect Running Command -->
<local:SettingContainer x:Name="AutoDetectRunningCommand"
x:Uid="Profile_AutoDetectRunningCommand"
ClearSettingValue="{x:Bind Profile.ClearAutoDetectRunningCommand}"
HasSettingValue="{x:Bind Profile.HasAutoDetectRunningCommand, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AutoDetectRunningCommandOverrideSource, Mode=OneWay}">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind Profile.AutoDetectRunningCommandList, Mode=OneWay}"
SelectedItem="{x:Bind Profile.CurrentAutoDetectRunningCommand, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
<!-- RightClickContextMenu -->
<local:SettingContainer x:Name="RightClickContextMenu"
x:Uid="Profile_RightClickContextMenu"

View File

@@ -1553,6 +1553,10 @@
<value>Flash window</value>
<comment>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.</comment>
</data>
<data name="Profile_BellStyleNotification.Content" xml:space="preserve">
<value>Send notification</value>
<comment>An option to choose from for the "bell style" setting. When selected, a Windows desktop notification (toast) is sent to notify the user.</comment>
</data>
<data name="ColorScheme_AddNewButton.Text" xml:space="preserve">
<value>Add new</value>
<comment>Button label that creates a new color scheme.</comment>
@@ -2748,4 +2752,92 @@
<value>Type to filter icons</value>
<comment>Placeholder text for a text box to filter and select an icon.</comment>
</data>
<!-- OutputNotificationStyle shared checkbox labels -->
<data name="Profile_OutputNotificationStyleTaskbar.Content" xml:space="preserve">
<value>Flash taskbar</value>
<comment>An option to choose from for a notification style setting. When selected, the taskbar is flashed.</comment>
</data>
<data name="Profile_OutputNotificationStyleAudible.Content" xml:space="preserve">
<value>Audible</value>
<comment>An option to choose from for a notification style setting. When selected, an audible cue is played.</comment>
</data>
<data name="Profile_OutputNotificationStyleTab.Content" xml:space="preserve">
<value>Tab indicator</value>
<comment>An option to choose from for a notification style setting. When selected, an activity indicator is shown on the tab.</comment>
</data>
<data name="Profile_OutputNotificationStyleNotification.Content" xml:space="preserve">
<value>Desktop notification</value>
<comment>An option to choose from for a notification style setting. When selected, a desktop toast notification is sent.</comment>
</data>
<data name="Profile_OutputNotificationStyleAll/Content" xml:space="preserve">
<value>All</value>
<comment>An option label for notification style. Shown when all notification styles are enabled.</comment>
</data>
<data name="Profile_OutputNotificationStyleNone/Content" xml:space="preserve">
<value>None</value>
<comment>An option label for notification style. Shown when no notification styles are enabled.</comment>
</data>
<!-- NotifyOnInactiveOutput -->
<data name="Profile_NotifyOnInactiveOutput.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Notify on inactive output</value>
<comment>Name for a control to select how the app notifies the user when an inactive pane produces output.</comment>
</data>
<data name="Profile_NotifyOnInactiveOutput.Header" xml:space="preserve">
<value>Notify on inactive output</value>
<comment>Header for a control to select how the app notifies the user when an inactive pane produces output.</comment>
</data>
<data name="Profile_NotifyOnInactiveOutput.HelpText" xml:space="preserve">
<value>Controls how you are notified when a background pane produces new output.</value>
<comment>A description for what the "notify on inactive output" setting does.</comment>
</data>
<!-- NotifyOnNextPrompt -->
<data name="Profile_NotifyOnNextPrompt.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Notify on next prompt</value>
<comment>Name for a control to select how the app notifies the user when a command finishes and the shell returns to a prompt.</comment>
</data>
<data name="Profile_NotifyOnNextPrompt.Header" xml:space="preserve">
<value>Notify on next prompt</value>
<comment>Header for a control to select how the app notifies the user when a command finishes and the shell returns to a prompt.</comment>
</data>
<data name="Profile_NotifyOnNextPrompt.HelpText" xml:space="preserve">
<value>Controls how you are notified when a running command finishes and the shell returns to a prompt. Requires shell integration.</value>
<comment>A description for what the "notify on next prompt" setting does.</comment>
</data>
<!-- AutoDetectRunningCommand -->
<data name="Profile_AutoDetectRunningCommand.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Auto-detect running command</value>
<comment>Name for a control to select how the app detects and indicates a running command.</comment>
</data>
<data name="Profile_AutoDetectRunningCommand.Header" xml:space="preserve">
<value>Auto-detect running command</value>
<comment>Header for a control to select how the app detects and indicates a running command.</comment>
</data>
<data name="Profile_AutoDetectRunningCommand.HelpText" xml:space="preserve">
<value>Controls whether and how the terminal automatically shows progress while a command is running. Requires shell integration.</value>
<comment>A description for what the "auto-detect running command" setting does.</comment>
</data>
<data name="Profile_AutoDetectRunningCommand_Disabled.Content" xml:space="preserve">
<value>Disabled</value>
<comment>An option to choose from for the "auto-detect running command" setting. When selected, no auto-detection occurs.</comment>
</data>
<data name="Profile_AutoDetectRunningCommand_Automatic.Content" xml:space="preserve">
<value>Automatic</value>
<comment>An option to choose from for the "auto-detect running command" setting. When selected, an indeterminate progress ring is shown while a command is running.</comment>
</data>
<data name="Profile_AutoDetectRunningCommand_Progress.Content" xml:space="preserve">
<value>Progress</value>
<comment>An option to choose from for the "auto-detect running command" setting. When selected, the terminal attempts to detect progress percentage from command output.</comment>
</data>
<data name="Profile_AutoDetectRunningCommandDisabled.Content" xml:space="preserve">
<value>Disabled</value>
<comment>An option to choose from for the "auto-detect running command" setting. When selected, no auto-detection occurs.</comment>
</data>
<data name="Profile_AutoDetectRunningCommandAutomatic.Content" xml:space="preserve">
<value>Automatic</value>
<comment>An option to choose from for the "auto-detect running command" setting. When selected, an indeterminate progress ring is shown while a command is running.</comment>
</data>
<data name="Profile_AutoDetectRunningCommandProgress.Content" xml:space="preserve">
<value>Progress</value>
<comment>An option to choose from for the "auto-detect running command" setting. When selected, the terminal attempts to detect progress percentage from command output.</comment>
</data>
</root>

View File

@@ -762,26 +762,84 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return til::hstring_format(FMT_COMPILE(L"{}, {}"), RS_switchable_(L"NewWindowCommandKey"), newTerminalArgsStr);
}
winrt::hstring PrevTabArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
static winrt::hstring _TabStatusFilterToString(TabStatusFilter filter)
{
if (!SwitcherMode())
if (filter == TabStatusFilter::All)
{
return RS_switchable_(L"PrevTabCommandKey");
return {};
}
const auto mode = SwitcherMode().Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order";
return til::hstring_format(FMT_COMPILE(L"{}, {}"), RS_switchable_(L"PrevTabCommandKey"), mode);
std::wstring result;
if (WI_IsFlagSet(filter, TabStatusFilter::Bell))
{
result += L"bell, ";
}
if (WI_IsFlagSet(filter, TabStatusFilter::Activity))
{
result += L"activity, ";
}
if (WI_IsFlagSet(filter, TabStatusFilter::Error))
{
result += L"error, ";
}
if (WI_IsFlagSet(filter, TabStatusFilter::Paused))
{
result += L"paused, ";
}
if (WI_IsFlagSet(filter, TabStatusFilter::Set))
{
result += L"set, ";
}
if (WI_IsFlagSet(filter, TabStatusFilter::Indeterminate))
{
result += L"indeterminate, ";
}
// Trim trailing ", "
if (result.size() >= 2)
{
result.resize(result.size() - 2);
}
return winrt::hstring{ result };
}
winrt::hstring PrevTabArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
{
winrt::hstring base;
if (!SwitcherMode())
{
base = RS_switchable_(L"PrevTabCommandKey");
}
else
{
const auto mode = SwitcherMode().Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order";
base = til::hstring_format(FMT_COMPILE(L"{}, {}"), RS_switchable_(L"PrevTabCommandKey"), mode);
}
const auto filterStr = _TabStatusFilterToString(Filter());
if (filterStr.empty())
{
return base;
}
return til::hstring_format(FMT_COMPILE(L"{}, filter: {}"), base, filterStr);
}
winrt::hstring NextTabArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
{
winrt::hstring base;
if (!SwitcherMode())
{
return RS_switchable_(L"NextTabCommandKey");
base = RS_switchable_(L"NextTabCommandKey");
}
const auto mode = SwitcherMode().Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order";
return til::hstring_format(FMT_COMPILE(L"{}, {}"), RS_switchable_(L"NextTabCommandKey"), mode);
else
{
const auto mode = SwitcherMode().Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order";
base = til::hstring_format(FMT_COMPILE(L"{}, {}"), RS_switchable_(L"NextTabCommandKey"), mode);
}
const auto filterStr = _TabStatusFilterToString(Filter());
if (filterStr.empty())
{
return base;
}
return til::hstring_format(FMT_COMPILE(L"{}, filter: {}"), base, filterStr);
}
winrt::hstring RenameWindowArgs::GenerateName(const winrt::WARC::ResourceContext& context) const

View File

@@ -236,11 +236,13 @@ protected: \
////////////////////////////////////////////////////////////////////////////////
#define PREV_TAB_ARGS(X) \
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, ArgTypeHint::None, nullptr)
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, ArgTypeHint::None, nullptr) \
X(TabStatusFilter, Filter, "filter", false, ArgTypeHint::None, TabStatusFilter::All)
////////////////////////////////////////////////////////////////////////////////
#define NEXT_TAB_ARGS(X) \
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, ArgTypeHint::None, nullptr)
X(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, "tabSwitcherMode", false, ArgTypeHint::None, nullptr) \
X(TabStatusFilter, Filter, "filter", false, ArgTypeHint::None, TabStatusFilter::All)
////////////////////////////////////////////////////////////////////////////////
#define RENAME_WINDOW_ARGS(X) \

View File

@@ -149,6 +149,19 @@ namespace Microsoft.Terminal.Settings.Model
All = 0xffffffff,
};
[flags]
enum TabStatusFilter
{
Bell = 0x1,
Activity = 0x2,
Error = 0x4,
Paused = 0x8,
Set = 0x10,
Indeterminate = 0x20,
Progress = 0x3C, // Error | Paused | Set | Indeterminate
All = 0xffffffff,
};
interface INewContentArgs {
String Type { get; };
Boolean Equals(INewContentArgs other);
@@ -403,15 +416,17 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass PrevTabArgs : IActionArgs, IActionArgsDescriptorAccess
{
PrevTabArgs();
PrevTabArgs(TabSwitcherMode SwitcherMode);
PrevTabArgs(TabSwitcherMode SwitcherMode, TabStatusFilter Filter);
Windows.Foundation.IReference<TabSwitcherMode> SwitcherMode;
TabStatusFilter Filter;
};
[default_interface] runtimeclass NextTabArgs : IActionArgs, IActionArgsDescriptorAccess
{
NextTabArgs();
NextTabArgs(TabSwitcherMode SwitcherMode);
NextTabArgs(TabSwitcherMode SwitcherMode, TabStatusFilter Filter);
Windows.Foundation.IReference<TabSwitcherMode> SwitcherMode;
TabStatusFilter Filter;
};
[default_interface] runtimeclass RenameWindowArgs : IActionArgs, IActionArgsDescriptorAccess

View File

@@ -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);
@@ -64,6 +65,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
DEFINE_ENUM_MAP(Microsoft::Terminal::Control::ScrollToMarkDirection, ScrollToMarkDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode, CommandPaletteLaunchMode);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::SuggestionsSource, SuggestionsSource);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::TabStatusFilter, TabStatusFilter);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::FindMatchDirection, FindMatchDirection);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::DesktopBehavior, DesktopBehavior);
DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::MonitorBehavior, MonitorBehavior);

View File

@@ -51,6 +51,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle> IntenseTextStyle();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Core::AdjustTextMode> AdjustIndistinguishableColors();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::PathTranslationStyle> PathTranslationStyle();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::AutoDetectRunningCommand> AutoDetectRunningCommand();
// Actions
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::ResizeDirection> ResizeDirection();
@@ -62,6 +63,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::ScrollToMarkDirection> ScrollToMarkDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode> CommandPaletteLaunchMode();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::SuggestionsSource> SuggestionsSource();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::TabStatusFilter> TabStatusFilter();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::FindMatchDirection> FindMatchDirection();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::DesktopBehavior> DesktopBehavior();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::MonitorBehavior> MonitorBehavior();

View File

@@ -33,6 +33,7 @@ namespace Microsoft.Terminal.Settings.Model
static Windows.Foundation.Collections.IMap<String, UInt16> FontWeight { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.IntenseStyle> IntenseTextStyle { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.PathTranslationStyle> PathTranslationStyle { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.AutoDetectRunningCommand> AutoDetectRunningCommand { get; };
// Actions
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.ResizeDirection> ResizeDirection { get; };
@@ -44,6 +45,7 @@ namespace Microsoft.Terminal.Settings.Model
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.ScrollToMarkDirection> ScrollToMarkDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode> CommandPaletteLaunchMode { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.SuggestionsSource> SuggestionsSource { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.TabStatusFilter> TabStatusFilter { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.FindMatchDirection> FindMatchDirection { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.DesktopBehavior> DesktopBehavior { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.MonitorBehavior> MonitorBehavior { get; };

View File

@@ -108,7 +108,10 @@ Author(s):
X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \
X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None)
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

View File

@@ -29,6 +29,7 @@ namespace Microsoft.Terminal.Settings.Model
Audible = 0x1,
Window = 0x2,
Taskbar = 0x4,
Notification = 0x8,
All = 0xffffffff
};
@@ -94,5 +95,9 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtClipboardWrite);
INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle);
INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnInactiveOutput);
INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.OutputNotificationStyle, NotifyOnNextPrompt);
INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.AutoDetectRunningCommand, AutoDetectRunningCommand);
}
}

View File

@@ -904,6 +904,9 @@
<data name="SwitcherModeActionArgumentLocalized" xml:space="preserve">
<value>Switcher mode</value>
</data>
<data name="FilterActionArgumentLocalized" xml:space="preserve">
<value>Filter</value>
</data>
<data name="QueryUrlActionArgumentLocalized" xml:space="preserve">
<value>Query URL</value>
</data>

View File

@@ -90,12 +90,13 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::MatchMode)
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::BellStyle)
{
static constexpr std::array<pair_type, 6> mappings = {
static constexpr std::array<pair_type, 7> mappings = {
pair_type{ "none", AllClear },
pair_type{ "audible", ValueType::Audible },
pair_type{ "visual", ValueType::Window | ValueType::Taskbar },
pair_type{ "window", ValueType::Window },
pair_type{ "taskbar", ValueType::Taskbar },
pair_type{ "notification", ValueType::Notification },
pair_type{ "all", AllSet },
};
@@ -119,6 +120,46 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::BellStyle)
}
};
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Control::OutputNotificationStyle)
{
static constexpr std::array<pair_type, 6> 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
@@ -519,6 +560,21 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SuggestionsSourc
};
};
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::TabStatusFilter)
{
static constexpr std::array<pair_type, 9> mappings = {
pair_type{ "none", AllClear },
pair_type{ "bell", ValueType::Bell },
pair_type{ "activity", ValueType::Activity },
pair_type{ "error", ValueType::Error },
pair_type{ "paused", ValueType::Paused },
pair_type{ "set", ValueType::Set },
pair_type{ "indeterminate", ValueType::Indeterminate },
pair_type{ "progress", ValueType::Progress },
pair_type{ "all", AllSet },
};
};
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::WindowingMode)
{
JSON_MAPPINGS(3) = {

View File

@@ -353,6 +353,25 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
#endif
}
// Toast notification activations launch a new unelevated instance of the
// app with "__fromToast" as the sole command-line argument. This happens
// when the originating Terminal window is elevated — Windows cannot
// COM-activate an elevated process from a toast click, so it starts a new
// unelevated process instead. That process must not restore persisted
// layouts, create windows, or do anything else — just exit immediately.
//
// We check this BEFORE the mutex handoff because the elevated instance
// owns the mutex and UIPI blocks WM_COPYDATA from our unelevated process,
// which would cause acquireMutexOrAttemptHandoff to spin for ~30s.
const auto args = commandlineToArgArray(GetCommandLineW());
{
if (args.size() == 2 && args[1] == L"__fromToast")
{
TerminateProcess(GetCurrentProcess(), 0);
__assume(false);
}
}
// Windows Terminal is a single-instance application. Either acquire ownership
// over the mutex, or hand off the command line to the existing instance.
const auto mutex = acquireMutexOrAttemptHandoff(windowClassName.c_str(), nCmdShow);
@@ -364,6 +383,21 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
__assume(false);
}
// When running without package identity, set an explicit AppUserModelID so
// that toast notifications (and other shell features) work correctly.
if (!IsPackaged())
{
#if defined(WT_BRANDING_RELEASE)
LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(L"Microsoft.WindowsTerminal"));
#elif defined(WT_BRANDING_PREVIEW)
LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(L"Microsoft.WindowsTerminalPreview"));
#elif defined(WT_BRANDING_CANARY)
LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(L"Microsoft.WindowsTerminalCanary"));
#else
LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(L"Microsoft.WindowsTerminalDev"));
#endif
}
_app = winrt::TerminalApp::App{};
_app.Logic().ReloadSettings();
@@ -406,8 +440,6 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
}
}
const auto args = commandlineToArgArray(GetCommandLineW());
if (args.size() == 2 && args[1] == L"-Embedding")
{
// We were launched for ConPTY handoff. We have no windows and also don't want to exit.

View File

@@ -87,4 +87,7 @@
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::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)

View File

@@ -424,7 +424,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.
}

View File

@@ -66,7 +66,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;

View File

@@ -85,7 +85,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;

View File

@@ -3583,7 +3583,7 @@ void AdaptDispatch::DoConEmuAction(const std::wstring_view string)
else if (subParam == 12)
{
_pages.ActivePage().Buffer().StartCommand();
_api.NotifyShellIntegrationMark();
_api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Command);
}
}
@@ -3614,7 +3614,7 @@ void AdaptDispatch::DoITerm2Action(const std::wstring_view string)
if (action == L"SetMark")
{
_pages.ActivePage().Buffer().StartPrompt();
_api.NotifyShellIntegrationMark();
_api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::Prompt);
}
}
@@ -3648,19 +3648,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
@@ -3681,7 +3681,7 @@ void AdaptDispatch::DoFinalTermAction(const std::wstring_view string)
}
_pages.ActivePage().Buffer().EndCurrentCommand(error);
_api.NotifyShellIntegrationMark();
_api.NotifyShellIntegrationMark(ITerminalApi::ShellIntegrationMark::CommandFinished);
break;
}

View File

@@ -207,7 +207,7 @@ public:
Log::Comment(L"NotifyBufferRotation MOCK called...");
}
void NotifyShellIntegrationMark() override
void NotifyShellIntegrationMark(ShellIntegrationMark /*mark*/) override
{
Log::Comment(L"NotifyShellIntegrationMark MOCK called...");
}

View File

@@ -0,0 +1,56 @@
# This script is re-entrant. Without a param, it will launch all the tabs. If
# you provide a param (e.g. "1"), it will launch just that tab, which is useful
# for testing the filtering behavior of nextTab/prevTab.
Param(
[string]$tab
)
if ($tab) {
# BEL character
$bel = [char]0x07
# Tab 1: plain (no indicators)
# Tab 2: bell
# Tab 3: progress "set" at 50%
# Tab 4: progress "error" at 75%
# Tab 5: progress "indeterminate"
# Tab 6: progress "paused" at 30%
switch ($tab) {
"1" { Write-Host 'Tab 1: plain (no status)' }`
"2" { Write-Host 'Tab 2: bell'; Start-Sleep -Milliseconds 1500; Write-Host -NoNewline $bel }
"3" { Write-Host 'Tab 3: progress set 50%'; Start-Sleep -Milliseconds 500; Write-Host -NoNewline ("`e]9;4;1;50`e\") }
"4" { Write-Host 'Tab 4: progress error 75%'; Start-Sleep -Milliseconds 500; Write-Host -NoNewline ("`e]9;4;2;75`e\") }
"5" { Write-Host 'Tab 5: progress indeterminate'; Start-Sleep -Milliseconds 500; Write-Host -NoNewline ("`e]9;4;3;0`e\") }
"6" { Write-Host 'Tab 6: progress paused 30%'; Start-Sleep -Milliseconds 500; Write-Host -NoNewline ("`e]9;4;4;30`e\") }
}
exit
}
# OSC 9;4 sequences for taskbar/progress state:
# state 1 = set, 2 = error, 3 = indeterminate, 4 = paused
# Format: ESC ] 9 ; 4 ; <state> ; <progress> ESC \
# $esc = [char]0x1b
# get the path to us
$scriptPath = $MyInvocation.MyCommand.Path
$pwsh = "C:\Program Files\PowerShell\7\pwsh.exe"
$i = 0
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 1`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 2`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 3`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 4`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 5`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 6`"
sleep 1
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 1`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 2`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 3`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 4`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 5`"
wtd -w test new-tab --tabTitle ${i++} "$pwsh" -NoExit -Command `"$scriptPath 6`"