Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OSC777 - send notification #14425

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ce375fa
send notifications, and get callbacks (in an entirely new instance. H…
zadjii-msft Sep 21, 2022
65bc163
stash, I never finished this before my kid was born
zadjii-msft Oct 31, 2022
67d8548
Merge branch 'main' into dev/migrie/fhl/7718-notifications
zadjii-msft Nov 21, 2022
006da6a
As a test, hook this up to BELs
zadjii-msft Nov 21, 2022
f0f75dc
Actually parse parameters from the notification
zadjii-msft Nov 21, 2022
e9b2e51
Plumbing is always the most work
zadjii-msft Nov 21, 2022
1fd87fe
only send when inactive
zadjii-msft Nov 22, 2022
054f173
cleanup
zadjii-msft Nov 22, 2022
0f339d2
revert some dead code
zadjii-msft Nov 22, 2022
8e170eb
oops
zadjii-msft Nov 22, 2022
3d83cc3
austinmode
zadjii-msft Nov 22, 2022
c4f623a
derp
zadjii-msft Nov 28, 2022
8b67ed7
more more austinmode
zadjii-msft Nov 28, 2022
9208222
I knew I forgot runformat
zadjii-msft Nov 28, 2022
cae6f04
Migrate spelling-0.0.21 changes from main
DHowett Nov 28, 2022
654416c
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/7718-n…
zadjii-msft Nov 30, 2022
7b524b0
simple nits from review
zadjii-msft Dec 1, 2022
6ac5137
unpackaged and elevated hate him
zadjii-msft Dec 1, 2022
75ea5f3
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/7718-n…
zadjii-msft Mar 3, 2023
dc448b4
Merge branch 'dev/migrie/fhl/7718-notifications' into dev/migrie/fhl/…
zadjii-msft Aug 24, 2023
3b02c96
summon didn't work but the rest did
zadjii-msft Aug 24, 2023
1726176
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/7718-n…
zadjii-msft Aug 28, 2023
015c5e8
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/7718-n…
zadjii-msft Feb 7, 2024
8309901
We do want this
zadjii-msft Feb 7, 2024
4ab628d
we do not want this
zadjii-msft Feb 7, 2024
d234049
Revert "we do not want this"
zadjii-msft Feb 7, 2024
d3a98b3
Merge branch 'dev/migrie/fhl/7718-notifications' of https://github.co…
zadjii-msft Feb 7, 2024
eac27db
spel
zadjii-msft Feb 7, 2024
9a6ded2
comments, disable with velocity
zadjii-msft Feb 8, 2024
70d905b
add velocity, and a setting per-control for this
zadjii-msft Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/allow/allow.txt
Expand Up @@ -117,6 +117,7 @@ uiatextrange
UIs
und
unregister
urxvt
versioned
vsdevcmd
walkthrough
Expand Down
31 changes: 31 additions & 0 deletions src/cascadia/Remoting/WindowManager.cpp
Expand Up @@ -461,4 +461,35 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
co_await winrt::resume_background();
_monarch.RequestSendContent(args);
}

// Attempt to summon an existing window. This static function does NOT
// pre-register as the monarch. This is used for activations from a
// notification, where this process should NEVER become its own window.
bool WindowManager::SummonForNotification(const uint64_t windowId)
Comment on lines +465 to +468
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what exactly is expected of this, but it doesn't seem to focus the Terminal window for me. If I've minimized it, it will be restored, but it's still hidden behind whatever other applications I've got open.

Also, if I've switched to another Terminal tab, I would have expected it to switch to the tab that triggered the notification, but that doesn't seem to be the case.

If that's not expected to work, though, that's fine. Just wanted to let you know in case there is a bug here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's still hidden behind whatever other applications I've got open

This is based off the same globalSummon code, which doesn't always work depending on the FG application. I know for a fact that Task Manager and OneNote don't allow you to summon the window on top of them - what did you have focused?

if I've switched to another Terminal tab, I would have expected it to switch to the tab that triggered the notification

That's something we should definitely do! I'd probably hold that for a follow-up though, to minimize the diffs. Right now I'm not really passing the tabIndex parameter, and there's not a good way to "globalSummon and switch to a tab" all in one go.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what did you have focused?

I've tried a few things now - Notepad, Firefox, Calculator - haven't yet found anything that worked. Even when I can see it being restored from a minimized state, it restores itself behind the active window.

Maybe it's just my version of Windows? (10.0.19044.2130)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh. That's... super weird. Does globalSummon work for you in this scenario?

We do play weird games with inserting a thread into the FG process so we can activate it (unsurprisingly, Windows does not make it easy for an app to bring itself to the FG). But if this didn't work, then I'd assume that globalSummon wouldn't work the same....

Unless there's something extra weird where the FG application is like, explorer.exe itself...

I'll spin this up in a VM to check.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does globalSummon work for you in this scenario?

Yeah. That seems to work in most cases. If it's minimized it pops up over the foreground app, and if it's open but in the background, it'll be moved to the foreground.

Although the latter case doesn't seem work with UWP apps I think (I've tried Calculator, Skype, News). If Terminal is hidden/minimized, global summon will open it up in the foreground, but if it's already open, and I've got something like Calculator focused, Terminal doesn't seem able to move itself to the foreground.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Finally got a chance to try this in the 10 VM. Seems like the notification will restore the Terminal from minimized, but won't bring it to the foreground. If it's open (but not in the FG), then this does seemingly nothing. Interesting.

I think this has to do with the way globalSummon attempts to bring a window to the foreground, but it interacts with the Windows 10 notifications in a VERY weird way. Usually to summon, we do this weird "inject a thread, then SetForeground on that thread" trick so that we
I suspect that when we get invoked from the Windows 10 notification, we can't

Now that I've typed that up, that can't be right.

In IslandWindow::_globalActivateWindow,

  • if minimized, we ShowWindow(..., SW_RESTORE);, then SetWindowPos(..., SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE) (to put on the right monitor). We're NOT doing the wacky FG trick here.
  • if not minimized, we AttachThreadInput+ShowWindow+SetActiveWindow+SetWindowPos to take foreground, activate ourselves, and move to the right monitor.

However, in neither of these cases does the Terminal get brought to the foreground from a notification in Windows 10. I'd think that the restore one should. Maybe there's a bug in Windows 10? Lemme reach out to a notifications expert.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thought - Outlook and Teams notifications never open the window in the foreground when you click on their notifications (certainly not on Windows 10). That's weird, but maybe a lead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it perhaps have something to do with Windows trying to prevent focus stealing? I don't know anything about the architecture, so maybe this doesn't apply, but could it help if you called AllowSetForegroundWindow somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost certainly.

You know, that's not a bad thought. That might require some wacky plumbing, but that might work. We'd need to get the PID of the target window to the WindowsTerminal.exe that got spawned, before we do then windowManager activation.... Might be possible? Certainly will be easier after #14843

{
auto monarch = create_instance<Remoting::IMonarch>(Monarch_clsid,
CLSCTX_LOCAL_SERVER);

if (monarch == nullptr)
{
return false;
}
SummonWindowSelectionArgs args{};
args.WindowID(windowId);

// Summon the window...
// * On its current desktop
// * Without a dropdown
// * On the monitor it is already on
// * Do not toggle, just make visible.
const Remoting::SummonWindowBehavior summonArgs{};
summonArgs.MoveToCurrentDesktop(false);
summonArgs.DropdownDuration(0);
summonArgs.ToMonitor(Remoting::MonitorBehavior::InPlace);
summonArgs.ToggleVisibility(false);

args.SummonBehavior(summonArgs);
monarch.SummonWindow(args);
return true;
}
}
2 changes: 2 additions & 0 deletions src/cascadia/Remoting/WindowManager.h
Expand Up @@ -47,6 +47,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex, Windows::Foundation::IReference<Windows::Foundation::Rect> windowBounds);
winrt::fire_and_forget RequestSendContent(Remoting::RequestReceiveContentArgs args);

static bool SummonForNotification(const uint64_t windowId);

TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);

TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/Remoting/WindowManager.idl
Expand Up @@ -29,6 +29,8 @@ namespace Microsoft.Terminal.Remoting
void RequestMoveContent(String window, String content, UInt32 tabIndex, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
void RequestSendContent(RequestReceiveContentArgs args);

static Boolean SummonForNotification(UInt64 windowId);

event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;

event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
Expand Down
110 changes: 110 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -14,6 +14,7 @@

#include <inc/WindowingBehavior.h>
#include <LibraryResources.h>
#include <WtExeUtils.h>
#include <TerminalCore/ControlKeyStates.hpp>
#include <til/latch.h>

Expand Down Expand Up @@ -45,6 +46,9 @@ using namespace ::Microsoft::Console;
using namespace ::Microsoft::Terminal::Core;
using namespace std::chrono_literals;

using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;

#define HOOKUP_ACTION(action) _actionDispatch->action({ this, &TerminalPage::_Handle##action });

namespace winrt
Expand Down Expand Up @@ -1671,6 +1675,7 @@ namespace winrt::TerminalApp::implementation
{
term.CompletionsChanged({ get_weak(), &TerminalPage::_ControlCompletionsChangedHandler });
}

winrt::weak_ref<TermControl> weakTerm{ term };
term.ContextMenu().Opening([weak = get_weak(), weakTerm](auto&& sender, auto&& /*args*/) {
if (const auto& page{ weak.get() })
Expand All @@ -1684,6 +1689,7 @@ namespace winrt::TerminalApp::implementation
page->_PopulateContextMenu(weakTerm.get(), sender.try_as<MUX::Controls::CommandBarFlyout>(), true);
}
});
term.SendNotification({ get_weak(), &TerminalPage::_SendNotificationHandler });
}

// Method Description:
Expand Down Expand Up @@ -2958,6 +2964,110 @@ namespace winrt::TerminalApp::implementation
_ShowWindowChangedHandlers(*this, args);
}

// Method Description:
// - Handler for a control's SendNotification event. `args` will contain the
// title and body of the notification requested by the client application.
// - This will only actually send a notification when the sender is
// - in an inactive window OR
// - in an inactive tab.
winrt::fire_and_forget TerminalPage::_SendNotificationHandler(const IInspectable sender,
const Microsoft::Terminal::Control::SendNotificationArgs args)
{
// This never works as expected when we're an elevated instance. The
// notification will end up launching an unelevated instance to handle
// it, and there's no good way to get back to the elevated one.
// Possibly revisit after GH #13276.
//
// We're using CanDragDrop, because TODO! I bet this works with UAC disabled
if (!CanDragDrop())
{
co_return;
}

auto weakThis = get_weak();

co_await resume_foreground(Dispatcher());
auto page{ weakThis.get() };
if (page)
{
// If the window is inactive, we always want to send the notification.
//
// Otherwise, we only want to send the notification for panes in inactive tabs.
if (_activated)
{
auto foundControl = false;
if (const auto activeTab{ _GetFocusedTabImpl() })
{
activeTab->GetRootPane()->WalkTree([&](auto&& pane) {
if (const auto& term{ pane->GetTerminalControl() })
{
if (term == sender)
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
{
foundControl = true;
return;
}
}
});
}

// The control that sent this is in the active tab. We
// should only send the notification if the window was
// inactive.
if (foundControl)
{
co_return;
}
}

_sendNotification(args.Title(), args.Body());
}
}

// Actually write the payload to a XML doc and load it into a ToastNotification.
void TerminalPage::_sendNotification(const std::wstring_view title,
const std::wstring_view body)
{
// ToastNotificationManager::CreateToastNotifier doesn't work in
// unpackaged scenarios without an AUMID. We probably don't have one if
// we're unpackaged. Unpackaged isn't a wholly supported scenario
// anyways, so let's just bail.

if (!IsPackaged())
{
return;
}

static winrt::hstring xmlTemplate{ L"\
<toast>\
<visual>\
<binding template=\"ToastGeneric\">\
<text></text>\
<text></text>\
</binding>\
</visual>\
</toast>" };
Comment on lines +3041 to +3048
Copy link
Member

@lhecker lhecker Nov 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our notification system uses full blown XML instead of data transfer objects?
...and instead of making it use structured data we added a AppNotifications.Builder that still spits out XML?

Why is WinRT so awful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lemme tell you, it's a disaster. At least the sample has like, 25% c++winrt code these days. The original sample was "Well, the API it hard to use, so go get this third party nuget package, and do some C#" 🤦


XmlDocument doc;
doc.LoadXml(xmlTemplate);
// Populate with text and values
auto payload{ fmt::format(L"window={}&tabIndex=0", WindowProperties().WindowId()) };
doc.DocumentElement().SetAttribute(L"launch", payload);
doc.SelectSingleNode(L"//text[1]").InnerText(title);
doc.SelectSingleNode(L"//text[2]").InnerText(body);

// Construct the notification
ToastNotification notification{ doc };

// lazy-init
if (!_toastNotifier)
{
_toastNotifier = ToastNotificationManager::CreateToastNotifier();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not blocking) If a user has multiple windows open, we'll have a toast notifier for each window. Is it worth it at all to have a shared toast notifier across all windows? Like, the monarch acts as the notifier and the rest just ping it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, theoretically, yea. My gut says the overhead though is not worth the plumbing. That feel right? 🤷

}

// And show it!
_toastNotifier.Show(notification);
}

// Method Description:
// - Paste text from the Windows Clipboard to the focused terminal
void TerminalPage::_PasteText()
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Expand Up @@ -287,6 +287,9 @@ namespace winrt::TerminalApp::implementation
__declspec(noinline) SuggestionsControl _loadSuggestionsElementSlowPath();
bool _suggestionsControlIs(winrt::Windows::UI::Xaml::Visibility visibility);

// todo! maybe move to TerminalWindow
winrt::Windows::UI::Notifications::ToastNotifier _toastNotifier{ nullptr };

winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);

void _ShowAboutDialog();
Expand Down Expand Up @@ -543,6 +546,9 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender);
winrt::com_ptr<TerminalTab> _senderOrFocusedTab(const IInspectable& sender);

winrt::fire_and_forget _SendNotificationHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SendNotificationArgs args);

void _sendNotification(const std::wstring_view title, const std::wstring_view body);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/pch.h
Expand Up @@ -27,6 +27,8 @@

#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Windows.ApplicationModel.Activation.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
Expand All @@ -35,6 +37,7 @@
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Input.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.UI.Text.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include <winrt/Windows.UI.Xaml.Automation.Peers.h>
Expand Down
10 changes: 10 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Expand Up @@ -126,6 +126,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnCompletionsChanged = [=](auto&& menuJson, auto&& replaceLength) { _terminalCompletionsChanged(menuJson, replaceLength); };
_terminal->CompletionsChangedCallback(pfnCompletionsChanged);

auto pfnSendNotification = std::bind(&ControlCore::_terminalSendNotification, this, std::placeholders::_1, std::placeholders::_2);
_terminal->SetSendNotificationCallback(pfnSendNotification);

// MSFT 33353327: Initialize the renderer in the ctor instead of Initialize().
// We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go.
// If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach
Expand Down Expand Up @@ -1585,6 +1588,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_midiAudio.PlayNote(reinterpret_cast<HWND>(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
}

void ControlCore::_terminalSendNotification(const std::wstring_view title,
const std::wstring_view body)
{
const auto e = winrt::make_self<implementation::SendNotificationArgs>(title, body);
_SendNotificationHandlers(*this, *e);
}

bool ControlCore::HasSelection() const
{
const auto lock = _terminal->LockForReading();
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Expand Up @@ -280,6 +280,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable);

TYPED_EVENT(Attached, IInspectable, IInspectable);

TYPED_EVENT(SendNotification, IInspectable, Control::SendNotificationArgs);
// clang-format on

private:
Expand Down Expand Up @@ -372,6 +374,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation

winrt::fire_and_forget _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength);

void _terminalSendNotification(const std::wstring_view title,
const std::wstring_view body);
#pragma endregion

MidiAudio _midiAudio;
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Expand Up @@ -190,5 +190,7 @@ namespace Microsoft.Terminal.Control

event Windows.Foundation.TypedEventHandler<Object, CompletionsChangedEventArgs> CompletionsChanged;

event Windows.Foundation.TypedEventHandler<Object, SendNotificationArgs> SendNotification;

};
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/EventArgs.cpp
Expand Up @@ -20,3 +20,4 @@
#include "KeySentEventArgs.g.cpp"
#include "CharSentEventArgs.g.cpp"
#include "StringSentEventArgs.g.cpp"
#include "SendNotificationArgs.g.cpp"
15 changes: 15 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.h
Expand Up @@ -20,6 +20,7 @@
#include "KeySentEventArgs.g.h"
#include "CharSentEventArgs.g.h"
#include "StringSentEventArgs.g.h"
#include "SendNotificationArgs.g.h"

namespace winrt::Microsoft::Terminal::Control::implementation
{
Expand Down Expand Up @@ -251,6 +252,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation

WINRT_PROPERTY(winrt::hstring, Text);
};

struct SendNotificationArgs : public SendNotificationArgsT<SendNotificationArgs>
{
public:
SendNotificationArgs(const std::wstring_view title,
const std::wstring_view body) :
_Title(title),
_Body(body)
{
}

WINRT_PROPERTY(winrt::hstring, Title);
WINRT_PROPERTY(winrt::hstring, Body);
};
}

namespace winrt::Microsoft::Terminal::Control::factory_implementation
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.idl
Expand Up @@ -121,4 +121,10 @@ namespace Microsoft.Terminal.Control
{
String Text { get; };
}

runtimeclass SendNotificationArgs
{
String Title { get; };
String Body { get; };
}
}
8 changes: 8 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Expand Up @@ -104,6 +104,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation

_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });

// Re-raise the event with us as the sender.
_core.SendNotification([weakThis = get_weak()](auto s, auto e) {
if (auto self{ weakThis.get() })
{
self->_SendNotificationHandlers(*self, e);
}
});

// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.h
Expand Up @@ -194,6 +194,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(KeySent, IInspectable, Control::KeySentEventArgs);
TYPED_EVENT(CharSent, IInspectable, Control::CharSentEventArgs);
TYPED_EVENT(StringSent, IInspectable, Control::StringSentEventArgs);
TYPED_EVENT(SendNotification, IInspectable, Control::SendNotificationArgs);
// clang-format on

WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.idl
Expand Up @@ -58,6 +58,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusFollowMouseRequested;
event Windows.Foundation.TypedEventHandler<Object, SendNotificationArgs> SendNotification;

event Windows.Foundation.TypedEventHandler<Object, CompletionsChangedEventArgs> CompletionsChanged;

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/ICoreSettings.idl
Expand Up @@ -28,6 +28,7 @@ namespace Microsoft.Terminal.Core
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> StartingTabColor;

Boolean AutoMarkPrompts;
Boolean AllowNotifications;

};

Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Expand Up @@ -95,6 +95,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
_startingTitle = settings.StartingTitle();
_trimBlockSelection = settings.TrimBlockSelection();
_autoMarkPrompts = settings.AutoMarkPrompts();
_allowNotifications = settings.AllowNotifications();

_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());

Expand Down Expand Up @@ -1160,6 +1161,11 @@ void Terminal::SetPlayMidiNoteCallback(std::function<void(const int, const int,
_pfnPlayMidiNote.swap(pfn);
}

void Terminal::SetSendNotificationCallback(std::function<void(std::wstring_view, std::wstring_view)> pfn) noexcept
{
_pfnSendNotification.swap(pfn);
}

void Terminal::BlinkCursor() noexcept
{
if (_selectionMode != SelectionInteractionMode::Mark)
Expand Down