Compare commits

...

1 Commits

36 changed files with 610 additions and 30 deletions

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,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "DesktopNotification.h"
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;
namespace winrt::TerminalApp::implementation
{
std::atomic<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 need to provide an AUMID, but that case is less common
// and toast notifications may not be supported without additional setup.
auto notifier = ToastNotificationManager::CreateToastNotifier();
notifier.Show(toast);
}
catch (...)
{
// Toast notification is a best-effort feature. If it fails (e.g., notifications
// are disabled, or the app is unpackaged without proper AUMID setup), we silently
// ignore the error.
LOG_CAUGHT_EXCEPTION();
}
}
}

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

@@ -16,6 +16,12 @@ namespace TerminalApp
Boolean FlashTaskbar { get; };
};
runtimeclass NotificationEventArgs
{
Microsoft.Terminal.Control.OutputNotificationStyle Style { get; };
Boolean OnlyWhenInactive { get; };
};
interface IPaneContent
{
Windows.UI.Xaml.FrameworkElement GetRoot();
@@ -41,6 +47,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]() {
@@ -1161,6 +1199,63 @@ namespace winrt::TerminalApp::implementation
}
});
events.NotificationRequested = content.NotificationRequested(
winrt::auto_revoke,
[dispatcher, weakThis](TerminalApp::IPaneContent sender, auto notificationArgs) -> 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 (notificationArgs.OnlyWhenInactive() && isActivePaneContent &&
tab->_focusState != WUX::FocusState::Unfocused)
{
co_return;
}
const auto style = notificationArgs.Style();
if (WI_IsFlagSet(style, 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 });
@@ -1393,6 +1488,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,
@@ -121,6 +124,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 +180,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 +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();

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);
@@ -1185,4 +1195,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

@@ -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))
@@ -298,6 +314,21 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPaneContent::_controlPromptStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
{
}
void TerminalPaneContent::_controlOutputStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
{
}
void TerminalPaneContent::_controlOutputIdleHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
{
}
safe_void_coroutine TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ get_weak() };

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
@@ -19,6 +20,16 @@ namespace winrt::TerminalApp::implementation
til::property<bool> FlashTaskbar;
};
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
{
TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
@@ -36,6 +47,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
{
@@ -71,6 +83,9 @@ namespace winrt::TerminalApp::implementation
{
winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged;
winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell;
winrt::Microsoft::Terminal::Control::TermControl::PromptStarted_revoker _PromptStarted;
winrt::Microsoft::Terminal::Control::TermControl::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 +104,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

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

@@ -751,6 +751,11 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s
// This changed the scrollbar marks - raise a notification to update them
_NotifyScrollEvent();
}
// regardless, start notify that we started command output
if (_pfnOutputStarted)
{
_pfnOutputStarted();
}
}
}
@@ -1265,6 +1270,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,23 @@ 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. It will always
// start a new instance of our exe.
//
// However, we're also able to just handle the .Activated event on the toast
// itself, so we don't care about this process we're spawning. So before we
// do _anything_ else, if we were created for a toast, just immediately
// bail.
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);
@@ -406,8 +423,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

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

@@ -417,8 +417,15 @@ function Invoke-CodeFormat() {
)
$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -find "**\x64\bin\clang-format.exe"
If ([String]::IsNullOrEmpty($clangFormatPath)) {
# try again with prerelease versions of Visual Studio,
# and just take the first
$clangFormatPath = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -prerelease -find "**\clang-format.exe" | Select-Object -First 1
}
If ([String]::IsNullOrEmpty($clangFormatPath)) {
Write-Error "No Visual Studio-supplied version of clang-format could be found."
return -1
}
$root = Find-OpenConsoleRoot

View File

@@ -2,4 +2,4 @@
rem run clang-format on c++ files
powershell -noprofile "import-module %OPENCON_TOOLS%\openconsole.psm1; Invoke-CodeFormat"
pwsh -noprofile "import-module %OPENCON_TOOLS%\openconsole.psm1; Invoke-CodeFormat"