Skip to content

Commit

Permalink
Add a setting to flash the taskbar when the terminal emits BEL (#8215)
Browse files Browse the repository at this point in the history
The terminal taskbar icon can now flash when the BEL sequence is
emitted, to let the user know something needs their attention. 

The `BellStyle` setting can now be set to `audible`, `visual` or both or
none. When the pane receives a BEL event and the `bellStyle` includes
`visual`, we bubble the event up all the way to `AppHost` to handle
flashing the taskbar. 

Closes #1608
  • Loading branch information
PankajBhojwani authored Nov 18, 2020
1 parent 16e8a84 commit 1fbcf34
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 13 deletions.
31 changes: 25 additions & 6 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,30 @@
"type": "string"
},
"BellStyle": {
"enum": [
"none",
"audible"
],
"type": "string"
"oneOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"audible",
"visual"
]
}
},
{
"type": "string",
"enum": [
"audible",
"visual",
"all",
"none"
]
}
]
},
"ProfileGuid": {
"default": "{}",
Expand Down Expand Up @@ -800,7 +819,7 @@
},
"bellStyle": {
"default": "audible",
"description": "Controls what happens when the application emits a BEL character. When set to \"audible\", the Terminal will play a sound. When set to \"none\", nothing will happen.",
"description": "Controls what happens when the application emits a BEL character. When set to \"all\", the Terminal will play a sound and flash the taskbar icon. An array of specific behaviors can also be used. Supported array values include `audible` and `visual`. When set to \"none\", nothing will happen.",
"$ref": "#/definitions/BellStyle"
},
"closeOnExit": {
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged);
FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged);
FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged);
FORWARDED_TYPED_EVENT(RaiseVisualBell, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, RaiseVisualBell);
FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress);
};
}
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.idl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FullscreenChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> AlwaysOnTopChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RaiseVisualBell;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
}
}
10 changes: 9 additions & 1 deletion src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ void Pane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/,
// - Plays a warning note when triggered by the BEL control character,
// using the sound configured for the "Critical Stop" system event.`
// This matches the behavior of the Windows Console host.
// - Will also flash the taskbar if the bellStyle setting for this profile
// has the 'visual' flag set
// Arguments:
// - <unused>
void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
Expand All @@ -366,11 +368,16 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
auto paneProfile = settings.FindProfile(_profile.value());
if (paneProfile)
{
if (paneProfile.BellStyle() == winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible)
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
{
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
}
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual))
{
// Bubble this event up to app host, starting with bubbling to the hosting tab
_PaneRaiseVisualBellHandlers(nullptr);
}
}
}

Expand Down Expand Up @@ -2060,3 +2067,4 @@ std::optional<SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane
}

DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DEFINE_EVENT(Pane, PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/Pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Pane : public std::enable_shared_from_this<Pane>

WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);

private:
struct SnapSizeResult;
Expand Down
14 changes: 14 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,19 @@ namespace winrt::TerminalApp::implementation
}
});

// The RaiseVisualBell event has been bubbled up to here from the pane,
// the next part of the chain is bubbling up to app logic, which will
// forward it to app host.
newTabImpl->TabRaiseVisualBell([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };

if (page && tab)
{
page->_raiseVisualBellHandlers(nullptr, nullptr);
}
});

auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem);
_ReapplyCompactTabSize();
Expand Down Expand Up @@ -2929,5 +2942,6 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, FocusModeChanged, _focusModeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, FullscreenChanged, _fullscreenChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, AlwaysOnTopChanged, _alwaysOnTopChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, RaiseVisualBell, _raiseVisualBellHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, SetTaskbarProgress, _setTaskbarProgressHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(FocusModeChanged, _focusModeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(FullscreenChanged, _fullscreenChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(AlwaysOnTopChanged, _alwaysOnTopChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RaiseVisualBell, _raiseVisualBellHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTaskbarProgress, _setTaskbarProgressHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);

Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalApp/TerminalTab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,16 @@ namespace winrt::TerminalApp::implementation
}
}
});

// Add a PaneRaiseVisualBell event handler to the Pane. When the pane emits this event,
// we need to bubble it all the way to app host. In this part of the chain we bubble it
// from the hosting tab to the page.
pane->PaneRaiseVisualBell([weakThis](auto&& /*s*/) {
if (auto tab{ weakThis.get() })
{
tab->_TabRaiseVisualBellHandlers();
}
});
}

// Method Description:
Expand Down Expand Up @@ -1022,4 +1032,5 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalTab.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);

private:
std::shared_ptr<Pane> _rootPane{ nullptr };
Expand Down
6 changes: 4 additions & 2 deletions src/cascadia/TerminalSettingsModel/Profile.idl
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ namespace Microsoft.Terminal.Settings.Model
Always
};

[flags]
enum BellStyle
{
None,
Audible
Audible = 0x1,
Visual = 0x2,
All = 0xffffffff
};

[default_interface] runtimeclass Profile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,33 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::TerminalControl::ScrollbarState)
};
};

JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::BellStyle)
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::BellStyle)
{
static constexpr std::array<pair_type, 2> mappings = {
pair_type{ "none", ValueType::None },
pair_type{ "audible", ValueType::Audible }
static constexpr std::array<pair_type, 4> mappings = {
pair_type{ "none", AllClear },
pair_type{ "audible", ValueType::Audible },
pair_type{ "visual", ValueType::Visual },
pair_type{ "all", AllSet },
};

auto FromJson(const Json::Value& json)
{
if (json.isBool())
{
return json.asBool() ? AllSet : AllClear;
}
return BaseFlagMapper::FromJson(json);
}

bool CanConvert(const Json::Value& json)
{
return BaseFlagMapper::CanConvert(json) || json.isBool();
}

Json::Value ToJson(const ::winrt::Microsoft::Terminal::Settings::Model::BellStyle& bellStyle)
{
return BaseFlagMapper::ToJson(bellStyle);
}
};

JSON_ENUM_MAPPER(std::tuple<::winrt::Windows::UI::Xaml::HorizontalAlignment, ::winrt::Windows::UI::Xaml::VerticalAlignment>)
Expand Down
12 changes: 12 additions & 0 deletions src/cascadia/WindowsTerminal/AppHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ void AppHost::Initialize()
_logic.FullscreenChanged({ this, &AppHost::_FullscreenChanged });
_logic.FocusModeChanged({ this, &AppHost::_FocusModeChanged });
_logic.AlwaysOnTopChanged({ this, &AppHost::_AlwaysOnTopChanged });
_logic.RaiseVisualBell({ this, &AppHost::_RaiseVisualBell });

_logic.Create();

Expand Down Expand Up @@ -397,6 +398,17 @@ void AppHost::_AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable
_window->SetAlwaysOnTop(_logic.AlwaysOnTop());
}

// Method Description
// - Called when the app wants to flash the taskbar, indicating to the user that
// something needs their attention
// Arguments
// - <unused>
void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&,
const winrt::Windows::Foundation::IInspectable&)
{
_window->FlashTaskbar();
}

// Method Description:
// - Called when the IslandWindow has received a WM_MOUSEWHEEL message. This can
// happen on some laptops, where their trackpads won't scroll inactive windows
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/WindowsTerminal/AppHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ class AppHost
const winrt::Windows::Foundation::IInspectable& arg);
void _AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _WindowMouseWheeled(const til::point coord, const int32_t delta);
};
9 changes: 9 additions & 0 deletions src/cascadia/WindowsTerminal/IslandWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,15 @@ void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop)
}
}

// Method Description
// - Flash the taskbar icon, indicating to the user that something needs their attention
void IslandWindow::FlashTaskbar()
{
// Using 'false' as the boolean argument ensures that the taskbar is
// only flashed if the app window is not focused
FlashWindow(_window.get(), false);
}

// Method Description:
// - Sets the taskbar progress indicator
// - We follow the ConEmu style for the state and progress values,
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/WindowsTerminal/IslandWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class IslandWindow :
void FullscreenChanged(const bool fullscreen);
void SetAlwaysOnTop(const bool alwaysOnTop);

void FlashTaskbar();
void SetTaskbarProgress(const size_t state, const size_t progress);

#pragma endregion
Expand Down

0 comments on commit 1fbcf34

Please sign in to comment.