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 11 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/cascadia/Remoting/WindowManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -801,4 +801,22 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
return nullptr;
}

// 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);
monarch.SummonWindow(args);
return true;
}
}
2 changes: 2 additions & 0 deletions src/cascadia/Remoting/WindowManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void UpdateActiveTabTitle(winrt::hstring title);
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();

static bool SummonForNotification(const uint64_t windowId);

TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/Remoting/WindowManager.idl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ namespace Microsoft.Terminal.Remoting
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();

static Boolean SummonForNotification(UInt64 windowId);

event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
Expand Down
33 changes: 33 additions & 0 deletions src/cascadia/TerminalApp/TabManagement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace ::TerminalApp;
using namespace ::Microsoft::Console;

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

namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
Expand Down Expand Up @@ -163,6 +166,36 @@ namespace winrt::TerminalApp::implementation
}
});

newTabImpl->RaiseNotification([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
if (page && tab)
{
// Construct the XML toast template
XmlDocument doc;
doc.LoadXml(L"\
<toast>\
<visual>\
<binding template=\"ToastGeneric\">\
<text></text>\
<text></text>\
</binding>\
</visual>\
</toast>");

// Populate with text and values
doc.DocumentElement().SetAttribute(L"launch", L"window=1&tabIndex=1");
doc.SelectSingleNode(L"//text[1]").InnerText(L"Notification from the Terminal");
doc.SelectSingleNode(L"//text[2]").InnerText(L"Check this out, it's a friggin notification!");
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

// Construct the notification
winrt::Windows::UI::Notifications::ToastNotification notif{ doc };
ToastNotifier toastNotifier{ ToastNotificationManager::CreateToastNotifier() };
// And show it!
toastNotifier.Show(notif);
}
});

newTabImpl->DuplicateRequested([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
Expand Down
79 changes: 79 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,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 @@ -1508,6 +1511,7 @@ namespace winrt::TerminalApp::implementation
});

term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
term.SendNotification({ get_weak(), &TerminalPage::_SendNotificationHandler });
}

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

winrt::fire_and_forget TerminalPage::_SendNotificationHandler(const IInspectable sender,
const Microsoft::Terminal::Control::SendNotificationArgs args)
{
auto weakThis = get_weak();

co_await resume_foreground(Dispatcher());
auto page{ weakThis.get() };
if (page)
{
// If the window is inactive, we alway want to send the notification.
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
//
// 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());
}
}

void TerminalPage::_sendNotification(const std::wstring_view title,
const std::wstring_view body)
{
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", WindowId()) };
doc.DocumentElement().SetAttribute(L"launch", payload);
doc.SelectSingleNode(L"//text[1]").InnerText(title);
doc.SelectSingleNode(L"//text[2]").InnerText(body);

// Construct the notification
winrt::Windows::UI::Notifications::ToastNotification notif{ doc };
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

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(notif);
Fixed Show fixed Hide fixed
}

// Method Description:
// - Paste text from the Windows Clipboard to the focused terminal
void TerminalPage::_PasteText()
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ namespace winrt::TerminalApp::implementation
int _renamerLayoutCount{ 0 };
bool _renamerPressedEnter{ false };

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 @@ -457,7 +459,9 @@ namespace winrt::TerminalApp::implementation
void _updateTabCloseButton(const winrt::Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem);

winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
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
5 changes: 5 additions & 0 deletions src/cascadia/TerminalApp/TerminalTab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::UI::Xaml::Controls;
using namespace winrt::Windows::System;

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

zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
Expand Down Expand Up @@ -1156,6 +1159,8 @@ namespace winrt::TerminalApp::implementation
tab->_TabRaiseVisualBellHandlers();
}

// tab->_RaiseNotificationHandlers();

// Show the bell indicator in the tab header
tab->ShowBellIndicator(true);

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalTab.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ namespace winrt::TerminalApp::implementation

WINRT_CALLBACK(ActivePaneChanged, winrt::delegate<>);
WINRT_CALLBACK(TabRaiseVisualBell, winrt::delegate<>);
WINRT_CALLBACK(RaiseNotification, winrt::delegate<>);
WINRT_CALLBACK(DuplicateRequested, winrt::delegate<>);
WINRT_CALLBACK(SplitTabRequested, winrt::delegate<>);
WINRT_CALLBACK(FindRequested, winrt::delegate<>);
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
#include <winrt/Windows.Media.Core.h>
#include <winrt/Windows.Media.Playback.h>

#include "winrt/Windows.Data.Xml.Dom.h"
#include "winrt/Windows.UI.Notifications.h"
#include "winrt/Windows.ApplicationModel.Activation.h"
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
Expand Down
10 changes: 10 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnPlayMidiNote = std::bind(&ControlCore::_terminalPlayMidiNote, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetPlayMidiNoteCallback(pfnPlayMidiNote);

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 @@ -1420,6 +1423,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)
{
auto e = winrt::make_self<implementation::SendNotificationArgs>(title, body);
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
_SendNotificationHandlers(*this, *e);
}

bool ControlCore::HasSelection() const
{
return _terminal->IsSelectionActive();
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(SendNotification, IInspectable, Control::SendNotificationArgs);
// clang-format on

private:
Expand Down Expand Up @@ -304,6 +305,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _terminalPlayMidiNote(const int noteNumber,
const int velocity,
const std::chrono::microseconds duration);
void _terminalSendNotification(const std::wstring_view title,
const std::wstring_view body);
#pragma endregion

MidiAudio _midiAudio;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,6 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, SendNotificationArgs> SendNotification;
};
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/EventArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
#include "FoundResultsArgs.g.cpp"
#include "ShowWindowArgs.g.cpp"
#include "UpdateSelectionMarkersEventArgs.g.cpp"
#include "SendNotificationArgs.g.cpp"
16 changes: 16 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "FoundResultsArgs.g.h"
#include "ShowWindowArgs.g.h"
#include "UpdateSelectionMarkersEventArgs.g.h"
#include "SendNotificationArgs.g.h"

namespace winrt::Microsoft::Terminal::Control::implementation
{
Expand Down Expand Up @@ -169,4 +170,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation

WINRT_PROPERTY(bool, ClearMarkers, false);
};

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

}
6 changes: 6 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,10 @@ namespace Microsoft.Terminal.Control
{
Boolean ClearMarkers { get; };
}

runtimeclass SendNotificationArgs
{
String Title { get; };
String Body { get; };
}
}
8 changes: 8 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });

// 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
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(FocusFollowMouseRequested, IInspectable, IInspectable);
TYPED_EVENT(Initialized, Control::TermControl, Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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<TermControl, Windows.UI.Xaml.RoutedEventArgs> Initialized;
// This is an event handler forwarder for the underlying connection.
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,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);
}

// Method Description:
// - Sets the cursor to be currently on. On/Off is tracked independently of
// cursor visibility (hidden/visible). On/off is controlled by the cursor
Expand Down
Loading