Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2849,6 +2849,78 @@
"description": "Sets the sound played when the application emits a BEL. When set to an array, the terminal will pick one of those sounds at random.",
"$ref": "#/$defs/BellSound"
},
"notifyOnInactiveOutput": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"taskbar",
"audible",
"tab",
"notification"
]
}
},
{
"type": "string",
"enum": [
"taskbar",
"audible",
"tab",
"notification",
"all",
"none"
]
}
],
"description": "Controls how you are notified when an inactive tab produces new output."
},
"notifyOnNextPrompt": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"taskbar",
"audible",
"tab",
"notification"
]
}
},
{
"type": "string",
"enum": [
"taskbar",
"audible",
"tab",
"notification",
"all",
"none"
]
}
],
"description": "Controls how you are notified when a new shell prompt is detected. Requires shell integration."
},
"autoDetectRunningCommand": {
"default": "disabled",
"description": "Automatically detect when a command is running and show a progress indicator in the tab and taskbar.",
"enum": [
"disabled",
"automatic",
"progress"
],
"type": "string"
},
"closeOnExit": {
"default": "automatic",
"description": "Sets how the profile reacts to termination or failure to launch. Possible values:\n -\"graceful\" (close when exit is typed or the process exits normally)\n -\"always\" (always close)\n -\"automatic\" (behave as \"graceful\" only for processes launched by terminal, behave as \"always\" otherwise)\n -\"never\" (never close).\ntrue and false are accepted as synonyms for \"graceful\" and \"never\" respectively.",
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/IPaneContent.idl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace TerminalApp

runtimeclass NotificationEventArgs
{
Microsoft.Terminal.Control.OutputNotificationStyle Style { get; };
Boolean OnlyWhenInactive { get; };
String Title { get; };
String Body { get; };
};
Expand Down
78 changes: 75 additions & 3 deletions src/cascadia/TerminalApp/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ namespace winrt::TerminalApp::implementation
_bellIndicatorTimer.Stop();
}

void Tab::_ActivityIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/)
{
ShowActivityIndicator(false);
_activityIndicatorTimer.Stop();
}

// Method Description:
// - Initializes a TabViewItem for this Tab instance.
// Arguments:
Expand Down Expand Up @@ -329,6 +335,10 @@ namespace winrt::TerminalApp::implementation
{
ShowBellIndicator(false);
}
if (_tabStatus.ActivityIndicator())
{
ShowActivityIndicator(false);
}
}
}

Expand Down Expand Up @@ -459,6 +469,26 @@ namespace winrt::TerminalApp::implementation
_bellIndicatorTimer.Start();
}

void Tab::ShowActivityIndicator(const bool show)
{
ASSERT_UI_THREAD();

_tabStatus.ActivityIndicator(show);
}

void Tab::ActivateActivityIndicatorTimer()
{
ASSERT_UI_THREAD();

if (!_activityIndicatorTimer)
{
_activityIndicatorTimer.Interval(std::chrono::milliseconds(2000));
_activityIndicatorTimer.Tick({ get_weak(), &Tab::_ActivityIndicatorTimerTick });
}

_activityIndicatorTimer.Start();
}

// Method Description:
// - Gets the title string of the last focused terminal control in our tree.
// Returns the empty string if there is no such control.
Expand Down Expand Up @@ -1068,6 +1098,7 @@ namespace winrt::TerminalApp::implementation
dispatcher,
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 200 },
.leading = true,
.trailing = true,
},
[weakThis]() {
Expand Down Expand Up @@ -1168,13 +1199,50 @@ namespace winrt::TerminalApp::implementation

events.NotificationRequested = content.NotificationRequested(
winrt::auto_revoke,
[dispatcher, weakThis](TerminalApp::IPaneContent /*sender*/, auto notifArgs) -> safe_void_coroutine {
[dispatcher, weakThis](TerminalApp::IPaneContent sender, auto notifArgs) -> safe_void_coroutine {
const auto weakThisCopy = weakThis;
co_await wil::resume_foreground(dispatcher);
if (const auto tab{ weakThisCopy.get() })
{
const auto title = notifArgs.Title().empty() ? tab->Title() : notifArgs.Title();
tab->TabToastNotificationRequested.raise(title, notifArgs.Body());
const auto activeContent = tab->GetActiveContent();
const auto isActivePaneContent = activeContent && activeContent == sender;

if (notifArgs.OnlyWhenInactive() && isActivePaneContent &&
tab->_focusState != WUX::FocusState::Unfocused)
{
co_return;
}

const auto style = notifArgs.Style();

if (WI_IsFlagSet(style, OutputNotificationStyle::Taskbar))
{
tab->TabRaiseVisualBell.raise();
}

if (WI_IsFlagSet(style, winrt::Microsoft::Terminal::Control::OutputNotificationStyle::Audible))
{
if (const auto termContent{ sender.try_as<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))
{
const auto title = notifArgs.Title().empty() ? tab->Title() : notifArgs.Title();
tab->TabToastNotificationRequested.raise(title, notifArgs.Body());
}
}
});

Expand Down Expand Up @@ -1405,6 +1473,10 @@ namespace winrt::TerminalApp::implementation
{
tab->ShowBellIndicator(false);
}
if (tab->_tabStatus.ActivityIndicator())
{
tab->ShowActivityIndicator(false);
}
}
});

Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalApp/Tab.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -212,6 +215,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();
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalApp/TabHeaderControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
4 changes: 2 additions & 2 deletions src/cascadia/TerminalApp/TabManagement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1268,8 +1268,8 @@ namespace winrt::TerminalApp::implementation
{
// The toast Activated callback runs on a background thread.
// Marshal to the UI thread for tab focus and window summon.
page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakPage{ page->get_weak() }, weakTab]() {
if (const auto p{ weakPage.get() })
page->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [weakThis, weakTab]() {
if (const auto p{ weakThis.get() })
{
if (const auto t{ weakTab.get() })
{
Expand Down
Loading
Loading