Compare commits

...

40 Commits

Author SHA1 Message Date
Mike Griese
6254cf94bc stick the extension content _behind_ a transparent pane, so that XAML can appear _above_ it
This will most certainly break with winui 3
2023-09-11 16:41:54 -05:00
Mike Griese
7492a35a94 okay the blue rect in the Terminal works if the Terminal is at 0,0 2023-09-11 16:19:00 -05:00
Mike Griese
869472a60b arbitrarily add some vague boundaries in the code 2023-09-11 14:09:25 -05:00
Mike Griese
d0170dcfe6 layers on layers of HWNDs 2023-09-11 13:17:58 -05:00
Mike Griese
9628ef7056 now, the child isn't actually a child at all - it's a toplevel that's locked to the size of the owner. This works 2023-09-11 10:55:51 -05:00
Mike Griese
d973f1de53 start doing something with tracking the position of the parent window instead 2023-09-11 10:24:05 -05:00
Mike Griese
018a918f75 quite a few dead ends here 2023-09-11 09:42:53 -05:00
Mike Griese
cb7a045aad We'll need a debug windowed app for where we're going 2023-09-11 06:08:38 -05:00
Mike Griese
6107c3e551 Merge branch 'dev/migrie/fhl/scratchpad-pane' into dev/migrie/f/sui-panes 2023-09-11 05:43:06 -05:00
Mike Griese
46469aa5e3 Merge branch 'dev/migrie/fhl/non-terminal-panes-2023' into dev/migrie/fhl/scratchpad-pane 2023-09-11 05:24:30 -05:00
Mike Griese
c869b47e13 Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/non-terminal-panes-2023 2023-09-11 05:22:43 -05:00
Mike Griese
9531069538 background brush, done 2023-08-07 15:17:09 -05:00
Mike Griese
521e301541 update settings should work now 2023-08-03 13:50:11 -05:00
Mike Griese
842326daa5 icons for non-terminal pane content 2023-08-03 11:31:57 -05:00
Mike Griese
fb7c80938b derp 2023-08-01 11:37:10 -05:00
Mike Griese
29d0d57656 this works better than it has any right to 2023-07-27 16:29:26 -05:00
Mike Griese
cbd61b0a7d POC: yea, this works 2023-07-27 15:55:05 -05:00
Mike Griese
1cc9835454 feature flags too 2023-07-27 13:58:44 -05:00
Mike Griese
86914bdfc1 Merge branch 'dev/migrie/fhl/non-terminal-panes-2023' into dev/migrie/fhl/scratchpad-pane 2023-07-25 13:28:25 -05:00
Mike Griese
e0b003ad4d Merge branch 'main' into dev/migrie/fhl/non-terminal-panes-2023 2023-07-24 14:10:40 -05:00
Mike Griese
f89368c19b [PARENT] try to use GetActiveTerminalControl less in TerminalTab
(cherry picked from commit 262d95aae5)
2023-07-20 10:59:41 -05:00
Mike Griese
5582e1bcc8 [PARENT] You know what, I just went for it.
(cherry picked from commit 63ba8e19fd)
2023-07-20 10:59:28 -05:00
Mike Griese
a23c1a24dc keybindings too 2023-07-20 07:39:02 -05:00
Mike Griese
5f9add4000 Merge branch 'dev/migrie/fhl/non-terminal-panes-2023' into dev/migrie/fhl/scratchpad-pane 2023-07-20 07:04:10 -05:00
Mike Griese
11126f9b37 Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/non-terminal-panes-2023 2023-07-20 07:02:16 -05:00
Mike Griese
e31202b0b8 Merge commit '6a10ea5' into dev/migrie/fhl/non-terminal-panes-2023 2023-07-20 07:02:04 -05:00
Mike Griese
e6dc314c17 Merge commit 'b4042ea' into dev/migrie/fhl/non-terminal-panes-2023 2023-07-19 16:22:03 -05:00
Mike Griese
2d4030683a Let's just make it experimental 2023-07-18 13:47:58 -05:00
Mike Griese
262d95aae5 [PARENT] try to use GetActiveTerminalControl less in TerminalTab 2023-07-18 13:47:38 -05:00
Mike Griese
63ba8e19fd [PARENT] You know what, I just went for it. 2023-07-18 13:21:18 -05:00
Mike Griese
1b39db7ab0 Single commit that adds the whole scratchpad and action 2023-07-18 12:58:55 -05:00
Mike Griese
2dd8f409b2 [TO PARENT] dead code 2023-07-18 11:48:33 -05:00
Mike Griese
049c043279 some last cleanups 2023-07-18 10:26:32 -05:00
Mike Griese
a1da6c117e huge shuffling so that pane content can raise events instead of relying on termcontrol 2023-07-18 10:13:44 -05:00
Mike Griese
7c9ffb0e02 snapping now uses an interface, so that it's not TermControl-specific 2023-07-18 06:06:07 -05:00
Mike Griese
84df8197d4 close event 2023-07-17 14:22:12 -05:00
Mike Griese
5b3aa54b56 move GetNewTerminalArgs into IPaneContent 2023-07-17 12:42:43 -05:00
Mike Griese
ef6bb8a73c hey look, it builds now 2023-07-17 12:35:27 -05:00
Mike Griese
4e144425f0 Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/non-terminal-panes-2023 2023-07-17 10:53:56 -05:00
Mike Griese
f353323a23 I wanted to do this in one shot but _zelda_ 2023-05-12 13:32:12 -05:00
32 changed files with 1929 additions and 617 deletions

View File

@@ -5,6 +5,8 @@
#include "App.h"
#include "TerminalPage.h"
#include "ScratchpadContent.h"
#include "OceanContent.h"
#include "../WinRTUtils/inc/WtExeUtils.h"
#include "../../types/inc/utils.hpp"
#include "Utils.h"
@@ -1404,7 +1406,7 @@ namespace winrt::TerminalApp::implementation
{
if (const auto activePane{ activeTab->GetActivePane() })
{
_restartPaneConnection(activePane);
_restartPaneConnection(activePane->GetContent().try_as<TerminalApp::TerminalPaneContent>(), nullptr);
}
}
args.Handled(true);
@@ -1419,4 +1421,24 @@ namespace winrt::TerminalApp::implementation
}
args.Handled(true);
}
void TerminalPage::_HandleOpenScratchpad(const IInspectable& sender,
const ActionEventArgs& args)
{
if (Feature_ScratchpadPane::IsEnabled())
{
auto scratchPane{ winrt::make_self<OceanContent>() };
scratchPane->SetHostingWindow(reinterpret_cast<uint64_t>(*_hostingHwnd));
// auto scratchPane{ winrt::make_self<SettingsPaneContent>(_settings) };
// This is maybe a little wacky - add our key event handler to the pane
// we made. So that we can get actions for keys that the content didn't
// handle.
scratchPane->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler });
auto resultPane = std::make_shared<Pane>(*scratchPane);
_SplitPane(_senderOrFocusedTab(sender), SplitDirection::Automatic, 0.5f, resultPane);
args.Handled(true);
}
}
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
runtimeclass BellEventArgs
{
Boolean FlashTaskbar { get; };
};
interface IPaneContent
{
Windows.UI.Xaml.FrameworkElement GetRoot();
void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
Windows.Foundation.Size MinSize { get; };
String Title { get; };
UInt64 TaskbarState { get; };
UInt64 TaskbarProgress { get; };
Boolean ReadOnly { get; };
String Icon { get; };
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
Microsoft.Terminal.Settings.Model.NewTerminalArgs GetNewTerminalArgs(Boolean asContent);
void Focus(Windows.UI.Xaml.FocusState reason);
void Close();
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, BellEventArgs> BellRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusRequested;
};
enum PaneSnapDirection
{
Width,
Height
};
interface ISnappable
{
Single SnapDownToGrid(PaneSnapDirection direction, Single sizeToSnap);
Windows.Foundation.Size GridSize { get; };
};
}

View File

@@ -0,0 +1,202 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "OceanContent.h"
#include "PaneArgs.h"
#include "OceanContent.g.cpp"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::TerminalApp::implementation
{
OceanContent::OceanContent()
{
_root = winrt::Windows::UI::Xaml::Controls::Grid{};
_root.VerticalAlignment(VerticalAlignment::Stretch);
_root.HorizontalAlignment(HorizontalAlignment::Stretch);
// auto res = Windows::UI::Xaml::Application::Current().Resources();
// auto bg = res.Lookup(winrt::box_value(L"UnfocusedBorderBrush"));
// _root.Background(bg.try_as<Media::Brush>());
// transparent
// return winrt::Windows::UI::Xaml::Media::SolidColorBrush{ winrt::Windows::UI::Colors::Transparent() };
// _box = winrt::Windows::UI::Xaml::Controls::TextBox{};
// _box.Margin({ 10, 10, 10, 10 });
// _box.AcceptsReturn(true);
// _box.TextWrapping(TextWrapping::Wrap);
// _root.Children().Append(_box);
_createOcean();
// Add a size change handler to the root grid.
_sizeChangedRevoker = _root.SizeChanged(winrt::auto_revoke, [this](auto&&, auto&&) {
_resizeToMatch();
});
}
void OceanContent::_resizeToMatch()
{
const auto widthInDips = _root.ActualWidth();
const auto heightInDips = _root.ActualHeight();
// wprintf(L"resized to: %f, %f\n", width, height);
// adjust for DPI
const auto dpi = Windows::Graphics::Display::DisplayInformation::GetForCurrentView().LogicalDpi();
const auto dpiScale = dpi / 96.0f;
const auto widthInPixels = widthInDips * dpiScale;
const auto heightInPixels = heightInDips * dpiScale;
// Get the actual location of our _root, relative to the screen
const auto transform = _root.TransformToVisual(nullptr);
const auto point = transform.TransformPoint({ 0, 0 });
// scale from DIPs to pixels:
// Resize our ocean HWND
SetWindowPos(_window.get(),
_hostingHwnd, // set it behind the parent
gsl::narrow_cast<int>(point.X * dpiScale) + 48,
gsl::narrow_cast<int>(point.Y * dpiScale) + 48,
gsl::narrow_cast<int>(widthInPixels) - 96,
gsl::narrow_cast<int>(heightInPixels) - 96,
/* SWP_NOZORDER | SWP_NOMOVE */ SWP_NOACTIVATE);
}
static OceanContent* GetThisFromHandle(HWND const window) noexcept
{
return reinterpret_cast<OceanContent*>(GetWindowLongPtr(window, GWLP_USERDATA));
}
LRESULT __stdcall OceanContent::s_WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
{
WINRT_ASSERT(window);
if (WM_NCCREATE == message)
{
auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
OceanContent* that = static_cast<OceanContent*>(cs->lpCreateParams);
that->_window = wil::unique_hwnd(window);
// return that->OnNcCreate(wparam, lparam);
SetWindowLongPtr(that->_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
return DefWindowProc(that->_window.get(), WM_NCCREATE, wparam, lparam);
}
else if (OceanContent* that = GetThisFromHandle(window))
{
return that->_messageHandler(message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT OceanContent::_messageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(_window.get(), &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_HIGHLIGHT + 1));
EndPaint(_window.get(), &ps);
break;
}
case WM_CLOSE:
case WM_DESTROY:
{
// PostQuitMessage(0);
CloseRequested.raise(*this, nullptr);
break;
}
}
return DefWindowProc(_window.get(), message, wparam, lparam);
}
void OceanContent::_createOcean()
{
static const auto oceanClassAtom = []() {
static const auto SCRATCH_WINDOW_CLASS = L"ocean_window_class";
WNDCLASSEXW scratchClass{ 0 };
scratchClass.cbSize = sizeof(WNDCLASSEXW);
scratchClass.style = CS_HREDRAW | CS_VREDRAW | /*CS_PARENTDC |*/ CS_DBLCLKS;
scratchClass.lpszClassName = SCRATCH_WINDOW_CLASS;
scratchClass.lpfnWndProc = s_WndProc;
scratchClass.cbWndExtra = 0; // GWL_CONSOLE_WNDALLOC; // this is required to store the owning thread/process override in NTUSER
auto windowClassAtom{ RegisterClassExW(&scratchClass) };
return windowClassAtom;
}();
const auto style = 0; // no resize border, no caption, etc.
const auto exStyle = 0;
CreateWindowExW(exStyle, // WS_EX_LAYERED,
reinterpret_cast<LPCWSTR>(oceanClassAtom),
L"Hello World",
style,
200, // CW_USEDEFAULT,
200, // CW_USEDEFAULT,
200, // CW_USEDEFAULT,
200, // CW_USEDEFAULT,
0, // owner
nullptr,
nullptr,
this);
SetWindowLong(_window.get(), GWL_STYLE, 0); //remove all window styles, cause it's created with WS_CAPTION even if we didn't ask for it
ShowWindow(_window.get(), SW_SHOW); //display window
_resizeToMatch();
}
void OceanContent::SetHostingWindow(uint64_t hostingWindow) noexcept
{
_hostingHwnd = reinterpret_cast<HWND>(hostingWindow);
// Set our HWND as owned by the parent's. We're always on top of them, but not explicitly a _child_.
::SetWindowLongPtrW(_window.get(), GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(_hostingHwnd));
}
void OceanContent::UpdateSettings(const CascadiaSettings& /*settings*/)
{
// Nothing to do.
}
winrt::Windows::UI::Xaml::FrameworkElement OceanContent::GetRoot()
{
return _root;
}
winrt::Windows::Foundation::Size OceanContent::MinSize()
{
return { 1, 1 };
}
void OceanContent::Focus(winrt::Windows::UI::Xaml::FocusState /*reason*/)
{
// _box.Focus(reason);
}
void OceanContent::Close()
{
CloseRequested.raise(*this, nullptr);
}
NewTerminalArgs OceanContent::GetNewTerminalArgs(const bool /* asContent */) const
{
return nullptr;
}
winrt::hstring OceanContent::Icon() const
{
static constexpr std::wstring_view glyph{ L"\xe70b" }; // QuickNote
return winrt::hstring{ glyph };
}
winrt::Windows::UI::Xaml::Media::Brush OceanContent::BackgroundBrush()
{
return _root.Background();
}
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "OceanContent.g.h"
namespace winrt::TerminalApp::implementation
{
struct OceanContent : OceanContentT<OceanContent>
{
OceanContent();
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
winrt::Windows::Foundation::Size MinSize();
void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
void Close();
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const;
winrt::hstring Title() { return L"Xaml Ocean"; }
uint64_t TaskbarState() { return 0; }
uint64_t TaskbarProgress() { return 0; }
bool ReadOnly() { return false; }
winrt::hstring Icon() const;
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept { return nullptr; }
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
void SetHostingWindow(uint64_t hostingWindow) noexcept;
til::typed_event<> CloseRequested;
til::typed_event<winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::BellEventArgs> BellRequested;
til::typed_event<> TitleChanged;
til::typed_event<> TabColorChanged;
til::typed_event<> TaskbarProgressChanged;
til::typed_event<> ReadOnlyChanged;
til::typed_event<> FocusRequested;
private:
winrt::Windows::UI::Xaml::Controls::Grid _root{ nullptr };
wil::unique_hwnd _window;
HWND _hostingHwnd;
winrt::Windows::UI::Xaml::Controls::Grid::SizeChanged_revoker _sizeChangedRevoker;
void _createOcean();
void _resizeToMatch();
LRESULT _messageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
static LRESULT __stdcall s_WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept;
};
}

View File

@@ -33,19 +33,21 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
static const int AnimationDurationInMilliseconds = 200;
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) :
_control{ control },
_lastActive{ lastFocused },
_profile{ profile }
Pane::Pane(const IPaneContent& content, const bool lastFocused) :
_content{ content },
_lastActive{ lastFocused }
{
_root.Children().Append(_borderFirst);
_borderFirst.Child(_control);
_setupControlEvents();
const auto& control{ _content.GetRoot() };
_borderFirst.Child(control);
// Register an event with the control to have it inform us when it gains focus.
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
_lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
if (control)
{
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ContentGotFocusHandler });
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler });
}
// When our border is tapped, make sure to transfer focus to our control.
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
@@ -102,19 +104,6 @@ Pane::Pane(std::shared_ptr<Pane> first,
});
}
void Pane::_setupControlEvents()
{
_controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &Pane::_ControlConnectionStateChangedHandler });
_controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { this, &Pane::_ControlWarningBellHandler });
_controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { this, &Pane::_CloseTerminalRequestedHandler });
_controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { this, &Pane::_RestartTerminalRequestedHandler });
_controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { this, &Pane::_ControlReadOnlyChangedHandler });
}
void Pane::_removeControlEvents()
{
_controlEvents = {};
}
// Method Description:
// - Extract the terminal settings from the current (leaf) pane's control
// to be used to create an equivalent control
@@ -129,55 +118,7 @@ NewTerminalArgs Pane::GetTerminalArgsForPane(const bool asContent) const
// Leaves are the only things that have controls
assert(_IsLeaf());
NewTerminalArgs args{};
auto controlSettings = _control.Settings();
args.Profile(controlSettings.ProfileName());
// If we know the user's working directory use it instead of the profile.
if (const auto dir = _control.WorkingDirectory(); !dir.empty())
{
args.StartingDirectory(dir);
}
else
{
args.StartingDirectory(controlSettings.StartingDirectory());
}
args.TabTitle(controlSettings.StartingTitle());
args.Commandline(controlSettings.Commandline());
args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle());
if (controlSettings.TabColor() || controlSettings.StartingTabColor())
{
til::color c;
// StartingTabColor is prioritized over other colors
if (const auto color = controlSettings.StartingTabColor())
{
c = til::color(color.Value());
}
else
{
c = til::color(controlSettings.TabColor().Value());
}
args.TabColor(winrt::Windows::Foundation::IReference<winrt::Windows::UI::Color>{ static_cast<winrt::Windows::UI::Color>(c) });
}
// TODO:GH#9800 - we used to be able to persist the color scheme that a
// TermControl was initialized with, by name. With the change to having the
// control own its own copy of its settings, this isn't possible anymore.
//
// We may be able to get around this by storing the Name in the Core::Scheme
// object. That would work for schemes set by the Terminal, but not ones set
// by VT, but that seems good enough.
// Only fill in the ContentId if absolutely needed. If you fill in a number
// here (even 0), we'll serialize that number, AND treat that action as an
// "attach existing" rather than a "create"
if (asContent)
{
args.ContentId(_control.ContentId());
}
return args;
return _content.GetNewTerminalArgs(asContent);
}
// Method Description:
@@ -1021,184 +962,6 @@ Pane::PaneNeighborSearch Pane::_FindPaneAndNeighbor(const std::shared_ptr<Pane>
return { nullptr, nullptr, offset };
}
// Method Description:
// - Called when our attached control is closed. Triggers listeners to our close
// event, if we're a leaf pane.
// - If this was called, and we became a parent pane (due to work on another
// thread), this function will do nothing (allowing the control's new parent
// to handle the event instead).
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
auto newConnectionState = ConnectionState::Closed;
if (const auto coreState = sender.try_as<ICoreState>())
{
newConnectionState = coreState.ConnectionState();
}
if (newConnectionState < ConnectionState::Closed)
{
// Pane doesn't care if the connection isn't entering a terminal state.
co_return;
}
const auto weakThis = weak_from_this();
co_await wil::resume_foreground(_root.Dispatcher());
const auto strongThis = weakThis.lock();
if (!strongThis)
{
co_return;
}
// It's possible that this event handler started being executed, scheduled
// on the UI thread, another child got created. So our control is
// actually no longer _our_ control, and instead could be a descendant.
//
// When the control's new Pane takes ownership of the control, the new
// parent will register its own event handler. That event handler will get
// fired after this handler returns, and will properly cleanup state.
if (!_IsLeaf())
{
co_return;
}
const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed)
{
// A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit".
// This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting
// in Terminal flashing open and immediately closed.
co_return;
}
if (_profile)
{
if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic)
{
// For 'automatic', we only care about the connection state if we were launched by Terminal
// Since we were launched via defterm, ignore the connection state (i.e. we treat the
// close on exit mode as 'always', see GH #13325 for discussion)
Close();
}
const auto mode = _profile.CloseOnExit();
if ((mode == CloseOnExitMode::Always) ||
((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed))
{
Close();
}
}
}
void Pane::_CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
// It's possible that this event handler started being executed, then before
// we got the lock, another thread created another child. So our control is
// actually no longer _our_ control, and instead could be a descendant.
//
// When the control's new Pane takes ownership of the control, the new
// parent will register its own event handler. That event handler will get
// fired after this handler returns, and will properly cleanup state.
if (!_IsLeaf())
{
return;
}
Close();
}
void Pane::_RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
if (!_IsLeaf())
{
return;
}
_RestartTerminalRequestedHandlers(shared_from_this());
}
winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ weak_from_this() };
co_await wil::resume_foreground(_root.Dispatcher());
if (auto pane{ weakThis.lock() })
{
if (!_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayerCreated = true;
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
if (_bellPlayer)
{
const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) };
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
_bellPlayer.Source(item);
_bellPlayer.Play();
}
}
}
// Method Description:
// - Plays a warning note when triggered by the BEL control character,
// using the sound configured for the "Critical Stop" system event.`
// This matches the behavior of the Windows Console host.
// - Will also flash the taskbar if the bellStyle setting for this profile
// has the 'visual' flag set
// Arguments:
// - <unused>
void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
{
if (!_IsLeaf())
{
return;
}
if (_profile)
{
// We don't want to do anything if nothing is set, so check for that first
if (static_cast<int>(_profile.BellStyle()) != 0)
{
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{ wil::ExpandEnvironmentStringsW<std::wstring>(sounds.GetAt(rand() % sounds.Size()).c_str()) };
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);
}
}
if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window))
{
_control.BellLightOn();
}
// raise the event with the bool value corresponding to the taskbar flag
_PaneRaiseBellHandlers(nullptr, WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Taskbar));
}
}
}
// Event Description:
// - Called when our control gains focus. We'll use this to trigger our GotFocus
// callback. The tab that's hosting us should have registered a callback which
@@ -1207,7 +970,7 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
// - <unused>
// Return Value:
// - <none>
void Pane::_ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
void Pane::_ContentGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const RoutedEventArgs& /* args */)
{
auto f = FocusState::Programmatic;
@@ -1222,7 +985,7 @@ void Pane::_ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectabl
// - Called when our control loses focus. We'll use this to trigger our LostFocus
// callback. The tab that's hosting us should have registered a callback which
// can be used to update its own internal focus state
void Pane::_ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& /* sender */,
void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectable& /* sender */,
const RoutedEventArgs& /* args */)
{
_LostFocusHandlers(shared_from_this());
@@ -1245,21 +1008,9 @@ void Pane::Close()
// and connections beneath it.
void Pane::Shutdown()
{
// Clear out our media player callbacks, and stop any playing media. This
// will prevent the callback from being triggered after we've closed, and
// also make sure that our sound stops when we're closed.
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
_bellPlayer = nullptr;
_bellPlayerCreated = false;
}
if (_IsLeaf())
{
_control.Close();
_content.Close();
}
else
{
@@ -1314,7 +1065,14 @@ TermControl Pane::GetLastFocusedTerminalControl()
{
if (p->_IsLeaf())
{
return p->_control;
if (const auto& terminalPane{ p->_content.try_as<TerminalPaneContent>() })
{
return terminalPane.GetTerminal();
}
else
{
return nullptr;
}
}
pane = p;
}
@@ -1322,7 +1080,15 @@ TermControl Pane::GetLastFocusedTerminalControl()
}
return _firstChild->GetLastFocusedTerminalControl();
}
return _control;
if (const auto& terminalPane{ _content.try_as<TerminalPaneContent>() })
{
return terminalPane.GetTerminal();
}
else
{
return nullptr;
}
}
// Method Description:
@@ -1334,7 +1100,14 @@ TermControl Pane::GetLastFocusedTerminalControl()
// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane.
TermControl Pane::GetTerminalControl()
{
return _IsLeaf() ? _control : nullptr;
if (const auto& terminalPane{ _getTerminalContent() })
{
return terminalPane.GetTerminal();
}
else
{
return nullptr;
}
}
// Method Description:
@@ -1381,7 +1154,11 @@ void Pane::SetActive()
Profile Pane::GetFocusedProfile()
{
auto lastFocused = GetActivePane();
return lastFocused ? lastFocused->_profile : nullptr;
if (const auto& terminalPane{ lastFocused->_getTerminalContent() })
{
return terminalPane.GetProfile();
}
return nullptr;
}
// Method Description:
@@ -1454,10 +1231,7 @@ void Pane::UpdateVisuals()
void Pane::_Focus()
{
_GotFocusHandlers(shared_from_this(), FocusState::Programmatic);
if (const auto& control = GetLastFocusedTerminalControl())
{
control.Focus(FocusState::Programmatic);
}
_content.Focus(FocusState::Programmatic);
}
// Method Description:
@@ -1496,6 +1270,14 @@ void Pane::_FocusFirstChild()
}
}
void Pane::UpdateSettings(const CascadiaSettings& settings)
{
if (_content)
{
_content.UpdateSettings(settings);
}
}
// Method Description:
// - Updates the settings of this pane, presuming that it is a leaf.
// Arguments:
@@ -1503,13 +1285,14 @@ void Pane::_FocusFirstChild()
// - profile: The profile from which these settings originated.
// Return Value:
// - <none>
void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile)
void Pane::UpdateTerminalSettings(const TerminalSettingsCreateResult& settings, const Profile& profile)
{
assert(_IsLeaf());
_profile = profile;
_control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings());
if (const auto& terminalPane{ _getTerminalContent() })
{
return terminalPane.UpdateTerminalSettings(settings, profile);
}
}
// Method Description:
@@ -1595,7 +1378,7 @@ std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
// reattached to a tree somewhere as the control moves with the pane.
// Return Value:
// - <none>
void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
void Pane::_CloseChild(const bool closeFirst, const bool /*isDetaching*/)
{
// If we're a leaf, then chances are both our children closed in close
// succession. We waited on the lock while the other child was closed, so
@@ -1631,35 +1414,16 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
_borders = _GetCommonBorders();
// take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed.
_control = remainingChild->_control;
_connectionState = remainingChild->_connectionState;
_profile = remainingChild->_profile;
_content = remainingChild->_content;
_id = remainingChild->Id();
_isDefTermSession = remainingChild->_isDefTermSession;
// Add our new event handler before revoking the old one.
_setupControlEvents();
// Revoke the old event handlers. Remove both the handlers for the panes
// themselves closing, and remove their handlers for their controls
// closing. At this point, if the remaining child's control is closed,
// they'll trigger only our event handler for the control's close.
// However, if we are detaching the pane we want to keep its control
// handlers since it is just getting moved.
if (!isDetaching)
{
closedChild->WalkTree([](auto p) {
if (p->_IsLeaf())
{
p->_removeControlEvents();
}
});
}
closedChild->Closed(closedChildClosedToken);
remainingChild->Closed(remainingChildClosedToken);
remainingChild->_removeControlEvents();
// If we or either of our children was focused, we want to take that
// focus from them.
@@ -1679,7 +1443,8 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
// Reattach the TermControl to our grid.
_root.Children().Append(_borderFirst);
_borderFirst.Child(_control);
const auto& control{ _content.GetRoot() };
_borderFirst.Child(control);
// Make sure to set our _splitState before focusing the control. If you
// fail to do this, when the tab handles the GotFocus event and asks us
@@ -1688,14 +1453,17 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
_splitState = SplitState::None;
// re-attach our handler for the control's GotFocus event.
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
_lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
if (control)
{
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ContentGotFocusHandler });
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler });
}
// If we're inheriting the "last active" state from one of our children,
// focus our control now. This should trigger our own GotFocus event.
if (usedToFocusClosedChildsTerminal || _lastActive)
{
_control.Focus(FocusState::Programmatic);
_content.Focus(FocusState::Programmatic);
// See GH#7252
// Manually fire off the GotFocus event. Typically, this is done
@@ -1734,15 +1502,6 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
// Remove the event handlers on the old children
remainingChild->Closed(remainingChildClosedToken);
closedChild->Closed(closedChildClosedToken);
if (!isDetaching)
{
closedChild->WalkTree([](auto p) {
if (p->_IsLeaf())
{
p->_removeControlEvents();
}
});
}
// Reset our UI:
_root.Children().Clear();
@@ -2161,7 +1920,7 @@ void Pane::_SetupEntranceAnimation()
auto child = isFirstChild ? _firstChild : _secondChild;
auto childGrid = child->_root;
// If we are splitting a parent pane this may be null
auto control = child->_control;
auto control = child->_content.GetRoot();
// Build up our animation:
// * it'll take as long as our duration (200ms)
// * it'll change the value of our property from 0 to secondSize
@@ -2482,9 +2241,6 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
if (_IsLeaf())
{
// revoke our handler - the child will take care of the control now.
_removeControlEvents();
// Remove our old GotFocus handler from the control. We don't want the
// control telling us that it's now focused, we want it telling its new
// parent.
@@ -2513,11 +2269,8 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
else
{
// Move our control, guid, isDefTermSession into the first one.
_firstChild = std::make_shared<Pane>(_profile, _control);
_firstChild->_connectionState = std::exchange(_connectionState, ConnectionState::NotConnected);
_profile = nullptr;
_control = { nullptr };
_firstChild->_isDefTermSession = _isDefTermSession;
_firstChild = std::make_shared<Pane>(_content);
_content = nullptr;
}
_splitState = actualSplitType;
@@ -2839,8 +2592,16 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension
// If requested size is already snapped, then both returned values equal this value.
Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
const auto direction{ widthOrHeight ? PaneSnapDirection::Width : PaneSnapDirection::Height };
if (_IsLeaf())
{
const auto& snappable{ _content.try_as<ISnappable>() };
if (!snappable)
{
return { dimension, dimension };
}
// If we're a leaf pane, align to the grid of controlling terminal
const auto minSize = _GetMinSize();
@@ -2851,8 +2612,10 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
return { minDimension, minDimension };
}
auto lower = _control.SnapDimensionToGrid(widthOrHeight, dimension);
if (widthOrHeight)
auto lower = snappable.SnapDownToGrid(widthOrHeight ? PaneSnapDirection::Width : PaneSnapDirection::Height,
dimension);
if (direction == PaneSnapDirection::Width)
{
lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0;
lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0;
@@ -2871,8 +2634,10 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
}
else
{
const auto cellSize = _control.CharacterDimensions();
const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height);
const auto cellSize = snappable.GridSize();
const auto higher = lower + (direction == PaneSnapDirection::Width ?
cellSize.Width :
cellSize.Height);
return { lower, higher };
}
}
@@ -2918,21 +2683,36 @@ void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& si
{
if (_IsLeaf())
{
// We're a leaf pane, so just add one more row or column (unless isMinimumSize
// is true, see below).
if (sizeNode.isMinimumSize)
const auto& snappable{ _content.try_as<ISnappable>() };
if (snappable)
{
// If the node is of its minimum size, this size might not be snapped (it might
// be, say, half a character, or fixed 10 pixels), so snap it upward. It might
// however be already snapped, so add 1 to make sure it really increases
// (not strictly necessary but to avoid surprises).
sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher;
// We're a leaf pane, so just add one more row or column (unless isMinimumSize
// is true, see below).
if (sizeNode.isMinimumSize)
{
// If the node is of its minimum size, this size might not be snapped (it might
// be, say, half a character, or fixed 10 pixels), so snap it upward. It might
// however be already snapped, so add 1 to make sure it really increases
// (not strictly necessary but to avoid surprises).
sizeNode.size = _CalcSnappedDimension(widthOrHeight,
sizeNode.size + 1)
.higher;
}
else
{
const auto cellSize = snappable.GridSize();
sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
}
}
else
{
const auto cellSize = _control.CharacterDimensions();
sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
// If we're a leaf that didn't have a TermControl, then just increment
// by one. We have to increment by _some_ value, because this is used in
// a while() loop to find the next bigger size we can snap to. But since
// a non-terminal control doesn't really care what size it's snapped to,
// we can just say "one pixel larger is the next snap point"
sizeNode.size += 1;
}
}
else
@@ -3037,7 +2817,7 @@ Size Pane::_GetMinSize() const
{
if (_IsLeaf())
{
auto controlSize = _control.MinimumSize();
auto controlSize = _content.MinSize();
auto newWidth = controlSize.Width;
auto newHeight = controlSize.Height;
@@ -3135,14 +2915,17 @@ int Pane::GetLeafPaneCount() const noexcept
// created via default handoff
void Pane::FinalizeConfigurationGivenDefault()
{
_isDefTermSession = true;
if (const auto& terminalPane{ _content.try_as<TerminalPaneContent>() })
{
terminalPane.MarkAsDefterm();
}
}
// Method Description:
// - Returns true if the pane or one of its descendants is read-only
bool Pane::ContainsReadOnly() const
{
return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
return _IsLeaf() ? _content.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
}
// Method Description:
@@ -3157,8 +2940,8 @@ void Pane::CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& s
{
if (_IsLeaf())
{
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(_control.TaskbarState(),
_control.TaskbarProgress()) };
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(_content.TaskbarState(),
_content.TaskbarProgress()) };
states.push_back(tbState);
}
else
@@ -3189,9 +2972,12 @@ void Pane::BroadcastKey(const winrt::Microsoft::Terminal::Control::TermControl&
const bool keyDown)
{
WalkTree([&](const auto& pane) {
if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly())
if (const auto& termControl{ pane->GetTerminalControl() })
{
pane->_control.RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown);
if (termControl != sourceControl && !termControl.ReadOnly())
{
termControl.RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown);
}
}
});
}
@@ -3202,9 +2988,12 @@ void Pane::BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl&
const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers)
{
WalkTree([&](const auto& pane) {
if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly())
if (const auto& termControl{ pane->GetTerminalControl() })
{
pane->_control.RawWriteChar(character, scanCode, modifiers);
if (termControl != sourceControl && !termControl.ReadOnly())
{
termControl.RawWriteChar(character, scanCode, modifiers);
}
}
});
}
@@ -3213,19 +3002,16 @@ void Pane::BroadcastString(const winrt::Microsoft::Terminal::Control::TermContro
const winrt::hstring& text)
{
WalkTree([&](const auto& pane) {
if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly())
if (const auto& termControl{ pane->GetTerminalControl() })
{
pane->_control.RawWriteString(text);
if (termControl != sourceControl && !termControl.ReadOnly())
{
termControl.RawWriteString(text);
}
}
});
}
void Pane::_ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*e*/)
{
UpdateVisuals();
}
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::_ComputeBorderColor()
{
if (_lastActive)
@@ -3233,7 +3019,7 @@ winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::_ComputeBorderColor()
return _themeResources.focusedBorderBrush;
}
if (_broadcastEnabled && (_IsLeaf() && !_control.ReadOnly()))
if (_broadcastEnabled && (_IsLeaf() && !_content.ReadOnly()))
{
return _themeResources.broadcastBorderBrush;
}

View File

@@ -21,6 +21,7 @@
#pragma once
#include "TaskbarState.h"
#include "TerminalPaneContent.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -61,8 +62,7 @@ struct PaneResources
class Pane : public std::enable_shared_from_this<Pane>
{
public:
Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const winrt::Microsoft::Terminal::Control::TermControl& control,
Pane(const winrt::TerminalApp::IPaneContent& content,
const bool lastFocused = false);
Pane(std::shared_ptr<Pane> first,
@@ -81,10 +81,15 @@ public:
// - If this is a branch/root pane, return nullptr.
winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const
{
return _profile;
if (const auto& c{ _content.try_as<winrt::TerminalApp::TerminalPaneContent>() })
{
return c.GetProfile();
}
return nullptr;
}
winrt::Windows::UI::Xaml::Controls::Grid GetRootElement();
winrt::TerminalApp::IPaneContent GetContent() const noexcept { return _IsLeaf() ? _content : nullptr; }
bool WasLastFocused() const noexcept;
void UpdateVisuals();
@@ -101,8 +106,9 @@ public:
BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId, const bool asContent = false, const bool asMovePane = false);
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane(const bool asContent = false) const;
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
void UpdateTerminalSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane,
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
@@ -218,7 +224,6 @@ public:
WINRT_CALLBACK(LostFocus, winrt::delegate<std::shared_ptr<Pane>>);
WINRT_CALLBACK(PaneRaiseBell, winrt::Windows::Foundation::EventHandler<bool>);
WINRT_CALLBACK(Detached, winrt::delegate<std::shared_ptr<Pane>>);
WINRT_CALLBACK(RestartTerminalRequested, winrt::delegate<std::shared_ptr<Pane>>);
private:
struct PanePoint;
@@ -238,10 +243,8 @@ private:
std::shared_ptr<Pane> _secondChild{ nullptr };
SplitState _splitState{ SplitState::None };
float _desiredSplitPosition;
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
bool _isDefTermSession{ false };
winrt::TerminalApp::IPaneContent _content{ nullptr };
#pragma endregion
std::optional<uint32_t> _id;
@@ -251,17 +254,6 @@ private:
winrt::event_token _firstClosedToken{ 0 };
winrt::event_token _secondClosedToken{ 0 };
struct ControlEventTokens
{
winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged;
winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell;
winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested;
winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested;
winrt::Microsoft::Terminal::Control::TermControl::ReadOnlyChanged_revoker _ReadOnlyChanged;
} _controlEvents;
void _setupControlEvents();
void _removeControlEvents();
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
@@ -270,13 +262,14 @@ private:
bool _zoomed{ false };
bool _broadcastEnabled{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
bool _bellPlayerCreated{ false };
bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;
void _SetupChildCloseHandlers();
bool _HasChild(const std::shared_ptr<Pane> child);
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const
{
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
}
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
@@ -307,19 +300,11 @@ private:
void _Focus();
void _FocusFirstChild();
winrt::fire_and_forget _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 _ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
void _ContentGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
void _ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e);
void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
@@ -330,8 +315,6 @@ private:
SplitState _convertAutomaticOrDirectionalSplitState(const winrt::Microsoft::Terminal::Settings::Model::SplitDirection& splitType) const;
winrt::fire_and_forget _playBellSound(winrt::Windows::Foundation::Uri uri);
// Function Description:
// - Returns true if the given direction can be used with the given split
// type.

View File

@@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "PaneArgs.h"
#include "BellEventArgs.g.cpp"

View File

@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "BellEventArgs.g.h"
namespace winrt::TerminalApp::implementation
{
struct BellEventArgs : public BellEventArgsT<BellEventArgs>
{
public:
BellEventArgs(bool flashTaskbar) :
FlashTaskbar(flashTaskbar) {}
til::property<bool> FlashTaskbar;
};
};

View File

@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ScratchpadContent.h"
#include "PaneArgs.h"
#include "ScratchpadContent.g.cpp"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::TerminalApp::implementation
{
ScratchpadContent::ScratchpadContent()
{
_root = winrt::Windows::UI::Xaml::Controls::Grid{};
_root.VerticalAlignment(VerticalAlignment::Stretch);
_root.HorizontalAlignment(HorizontalAlignment::Stretch);
auto res = Windows::UI::Xaml::Application::Current().Resources();
auto bg = res.Lookup(winrt::box_value(L"UnfocusedBorderBrush"));
_root.Background(bg.try_as<Media::Brush>());
_box = winrt::Windows::UI::Xaml::Controls::TextBox{};
_box.Margin({ 10, 10, 10, 10 });
_box.AcceptsReturn(true);
_box.TextWrapping(TextWrapping::Wrap);
_root.Children().Append(_box);
}
void ScratchpadContent::UpdateSettings(const CascadiaSettings& /*settings*/)
{
// Nothing to do.
}
winrt::Windows::UI::Xaml::FrameworkElement ScratchpadContent::GetRoot()
{
return _root;
}
winrt::Windows::Foundation::Size ScratchpadContent::MinSize()
{
return { 1, 1 };
}
void ScratchpadContent::Focus(winrt::Windows::UI::Xaml::FocusState reason)
{
_box.Focus(reason);
}
void ScratchpadContent::Close()
{
CloseRequested.raise(*this, nullptr);
}
NewTerminalArgs ScratchpadContent::GetNewTerminalArgs(const bool /* asContent */) const
{
return nullptr;
}
winrt::hstring ScratchpadContent::Icon() const
{
static constexpr std::wstring_view glyph{ L"\xe70b" }; // QuickNote
return winrt::hstring{ glyph };
}
winrt::Windows::UI::Xaml::Media::Brush ScratchpadContent::BackgroundBrush()
{
return _root.Background();
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "ScratchpadContent.g.h"
namespace winrt::TerminalApp::implementation
{
struct ScratchpadContent : ScratchpadContentT<ScratchpadContent>
{
ScratchpadContent();
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
winrt::Windows::Foundation::Size MinSize();
void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
void Close();
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const;
winrt::hstring Title() { return L"Scratchpad"; }
uint64_t TaskbarState() { return 0; }
uint64_t TaskbarProgress() { return 0; }
bool ReadOnly() { return false; }
winrt::hstring Icon() const;
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept { return nullptr; }
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
til::typed_event<> CloseRequested;
til::typed_event<winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::BellEventArgs> BellRequested;
til::typed_event<> TitleChanged;
til::typed_event<> TabColorChanged;
til::typed_event<> TaskbarProgressChanged;
til::typed_event<> ReadOnlyChanged;
til::typed_event<> FocusRequested;
private:
winrt::Windows::UI::Xaml::Controls::Grid _root{ nullptr };
winrt::Windows::UI::Xaml::Controls::TextBox _box{ nullptr };
};
}

View File

@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "SettingsPaneContent.h"
#include "PaneArgs.h"
#include "SettingsPaneContent.g.cpp"
#include "Utils.h"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Microsoft::Terminal::Settings::Model;
#define ASSERT_UI_THREAD() assert(_sui.Dispatcher().HasThreadAccess())
namespace winrt::TerminalApp::implementation
{
SettingsPaneContent::SettingsPaneContent(CascadiaSettings settings)
{
_sui = winrt::Microsoft::Terminal::Settings::Editor::MainPage{ settings };
// Stash away the current requested theme of the app. We'll need that in
// _BackgroundBrush() to do a theme-aware resource lookup
_requestedTheme = settings.GlobalSettings().CurrentTheme().RequestedTheme();
}
void SettingsPaneContent::UpdateSettings(const CascadiaSettings& settings)
{
ASSERT_UI_THREAD();
_sui.UpdateSettings(settings);
_requestedTheme = settings.GlobalSettings().CurrentTheme().RequestedTheme();
}
winrt::Windows::UI::Xaml::FrameworkElement SettingsPaneContent::GetRoot()
{
return _sui;
}
winrt::Windows::Foundation::Size SettingsPaneContent::MinSize()
{
return { 1, 1 };
}
void SettingsPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason)
{
if (reason != FocusState::Unfocused)
{
_sui.as<Controls::Page>().Focus(reason);
}
}
void SettingsPaneContent::Close()
{
CloseRequested.raise(*this, nullptr);
}
NewTerminalArgs SettingsPaneContent::GetNewTerminalArgs(const bool /* asContent */) const
{
// TODO! hey, can we somehow replicate std::vector<ActionAndArgs> SettingsTab::BuildStartupActions?
return nullptr;
}
winrt::hstring SettingsPaneContent::Icon() const
{
// This is the Setting icon (looks like a gear)
static constexpr std::wstring_view glyph{ L"\xE713" };
return winrt::hstring{ glyph };
}
Windows::Foundation::IReference<winrt::Windows::UI::Color> SettingsPaneContent::TabColor() const noexcept
{
return nullptr;
}
winrt::Windows::UI::Xaml::Media::Brush SettingsPaneContent::BackgroundBrush()
{
// Look up the color we should use for the settings tab item from our
// resources. This should only be used for when "terminalBackground" is
// requested.
static const auto key = winrt::box_value(L"SettingsUiTabBrush");
// You can't just do a Application::Current().Resources().TryLookup
// lookup, cause the app theme never changes! Do the hacky version
// instead.
return ThemeLookup(Application::Current().Resources(), _requestedTheme, key).try_as<winrt::Windows::UI::Xaml::Media::Brush>();
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "SettingsPaneContent.g.h"
#include <LibraryResources.h>
namespace winrt::TerminalApp::implementation
{
struct SettingsPaneContent : SettingsPaneContentT<SettingsPaneContent>
{
SettingsPaneContent(winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings settings);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
winrt::Microsoft::Terminal::Settings::Editor::MainPage SettingsUI() { return _sui; }
winrt::Windows::Foundation::Size MinSize();
void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
void Close();
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const;
winrt::hstring Title() { return RS_(L"SettingsTab"); }
uint64_t TaskbarState() { return 0; }
uint64_t TaskbarProgress() { return 0; }
bool ReadOnly() { return false; }
winrt::hstring Icon() const;
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept;
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
til::typed_event<> CloseRequested;
til::typed_event<winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::BellEventArgs> BellRequested;
til::typed_event<> TitleChanged;
til::typed_event<> TabColorChanged;
til::typed_event<> TaskbarProgressChanged;
til::typed_event<> ReadOnlyChanged;
til::typed_event<> FocusRequested;
private:
winrt::Microsoft::Terminal::Settings::Editor::MainPage _sui{ nullptr };
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(SettingsPaneContent);
}

View File

@@ -63,7 +63,7 @@ namespace winrt::TerminalApp::implementation
// - existingConnection: An optional connection that is already established to a PTY
// for this tab to host instead of creating one.
// If not defined, the tab will create the connection.
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection)
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs)
try
{
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
@@ -86,7 +86,7 @@ namespace winrt::TerminalApp::implementation
//
// This call to _MakePane won't return nullptr, we already checked that
// case above with the _maybeElevate call.
_CreateNewTabFromPane(_MakePane(newTerminalArgs, nullptr, existingConnection));
_CreateNewTabFromPane(_MakePane(newTerminalArgs, nullptr));
return S_OK;
}
CATCH_RETURN();
@@ -171,14 +171,8 @@ namespace winrt::TerminalApp::implementation
auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().InsertAt(insertPosition, tabViewItem);
// Set this tab's icon to the icon from the user's profile
if (const auto profile{ newTabImpl->GetFocusedProfile() })
{
if (!profile.Icon().empty())
{
newTabImpl->UpdateIcon(profile.Icon());
}
}
// Set this tab's icon to the icon from the content
_UpdateTabIcon(*newTabImpl);
tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabClick });
@@ -223,13 +217,15 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - pane: The pane to use as the root.
// - insertPosition: Optional parameter to indicate the position of tab.
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition)
TerminalApp::TerminalTab TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition)
{
if (pane)
{
auto newTabImpl = winrt::make_self<TerminalTab>(pane);
_InitializeTab(newTabImpl, insertPosition);
return *newTabImpl;
}
return nullptr;
}
// Method Description:
@@ -239,9 +235,10 @@ namespace winrt::TerminalApp::implementation
// - tab: the Tab to update the title for.
void TerminalPage::_UpdateTabIcon(TerminalTab& tab)
{
if (const auto profile = tab.GetFocusedProfile())
if (const auto content{ tab.GetActiveContent() })
{
tab.UpdateIcon(profile.Icon());
const auto& icon{ content.Icon() };
tab.UpdateIcon(icon);
}
}

View File

@@ -134,6 +134,9 @@
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Pane.h" />
<ClInclude Include="PaneArgs.h">
<DependentUpon>IPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ColorHelper.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="ShortcutActionDispatch.h">
@@ -161,6 +164,18 @@
<ClInclude Include="SettingsLoadEventArgs.h">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalPaneContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ScratchpadContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="OceanContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SettingsPaneContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Toast.h" />
<ClInclude Include="SuggestionsControl.h">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
@@ -236,6 +251,9 @@
<DependentUpon>EmptyStringVisibilityConverter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Pane.cpp" />
<ClCompile Include="PaneArgs.cpp">
<DependentUpon>IPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
<ClCompile Include="ColorHelper.cpp" />
<ClCompile Include="DebugTapConnection.cpp" />
@@ -266,6 +284,18 @@
<ClCompile Include="TerminalWindow.cpp">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalPaneContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ScratchpadContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="OceanContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="SettingsPaneContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="Toast.cpp" />
<ClCompile Include="SuggestionsControl.cpp">
@@ -340,6 +370,8 @@
</Midl>
<Midl Include="FilteredCommand.idl" />
<Midl Include="EmptyStringVisibilityConverter.idl" />
<Midl Include="IPaneContent.idl" />
<Midl Include="TerminalPaneContent.idl" />
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>

View File

@@ -22,6 +22,7 @@
#include "ColorHelper.h"
#include "DebugTapConnection.h"
#include "SettingsTab.h"
#include "SettingsPaneContent.h"
#include "TabRowControl.h"
#include "Utils.h"
@@ -1287,15 +1288,20 @@ namespace winrt::TerminalApp::implementation
return connection;
}
TerminalConnection::ITerminalConnection TerminalPage::_duplicateConnectionForRestart(std::shared_ptr<Pane> pane)
TerminalConnection::ITerminalConnection TerminalPage::_duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent)
{
const auto& control{ pane->GetTerminalControl() };
if (paneContent == nullptr)
{
return nullptr;
}
const auto& control{ paneContent.GetTerminal() };
if (control == nullptr)
{
return nullptr;
}
const auto& connection = control.Connection();
auto profile{ pane->GetProfile() };
auto profile{ paneContent.GetProfile() };
TerminalSettingsCreateResult controlSettings{ nullptr };
@@ -2329,6 +2335,12 @@ namespace winrt::TerminalApp::implementation
}
}
// For now, prevent splitting the _settingsTab. We can always revisit this later.
if (*activeTab == _settingsTab)
{
return;
}
// If the caller is calling us with the return value of _MakePane
// directly, it's possible that nullptr was returned, if the connections
// was supposed to be launched in an elevated window. In that case, do
@@ -3019,10 +3031,9 @@ namespace winrt::TerminalApp::implementation
// Don't need to worry about duplicating or anything - we'll
// serialize the actual profile's GUID along with the content guid.
const auto& profile = _settings.GetProfileForArgs(newTerminalArgs);
const auto control = _AttachControlToContent(newTerminalArgs.ContentId());
return std::make_shared<Pane>(profile, control);
auto terminalPane{ winrt::make<TerminalPaneContent>(profile, control) };
return std::make_shared<Pane>(terminalPane);
}
TerminalSettingsCreateResult controlSettings{ nullptr };
@@ -3078,13 +3089,15 @@ namespace winrt::TerminalApp::implementation
const auto control = _CreateNewControlAndContent(controlSettings, connection);
auto resultPane = std::make_shared<Pane>(profile, control);
auto terminalPane{ winrt::make<TerminalPaneContent>(profile, control) };
auto resultPane = std::make_shared<Pane>(terminalPane);
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
auto newControl = _CreateNewControlAndContent(controlSettings, debugConnection);
// Split (auto) with the debug tap.
auto debugPane = std::make_shared<Pane>(profile, newControl);
auto debugTerminalPane{ winrt::make<TerminalPaneContent>(profile, newControl) };
auto debugPane = std::make_shared<Pane>(debugTerminalPane);
// Since we're doing this split directly on the pane (instead of going through TerminalTab,
// we need to handle the panes 'active' states
@@ -3098,16 +3111,18 @@ namespace winrt::TerminalApp::implementation
original->SetActive();
}
resultPane->RestartTerminalRequested({ get_weak(), &TerminalPage::_restartPaneConnection });
terminalPane.RestartTerminalRequested({ get_weak(), &TerminalPage::_restartPaneConnection });
return resultPane;
}
void TerminalPage::_restartPaneConnection(const std::shared_ptr<Pane>& pane)
void TerminalPage::_restartPaneConnection(
const TerminalApp::TerminalPaneContent& paneContent,
const winrt::Windows::Foundation::IInspectable&)
{
if (const auto& connection{ _duplicateConnectionForRestart(pane) })
if (const auto& connection{ _duplicateConnectionForRestart(paneContent) })
{
pane->GetTerminalControl().Connection(connection);
paneContent.GetTerminal().Connection(connection);
connection.Start();
}
}
@@ -3209,11 +3224,17 @@ namespace winrt::TerminalApp::implementation
{
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
{
terminalTab->UpdateSettings();
// Let the tab know that there are new settings. It's up to each content to decide what to do with them.
terminalTab->UpdateSettings(_settings);
// FURTHERMORE We need to do a bit more work here for terminal
// panes. They need to know about the profile that was used for
// them, and about the focused/unfocused settings.
// Manually enumerate the panes in each tab; this will let us recycle TerminalSettings
// objects but only have to iterate one time.
terminalTab->GetRootPane()->WalkTree([&](auto&& pane) {
// If the pane isn't a terminal pane, it won't have a profile.
if (const auto profile{ pane->GetProfile() })
{
const auto found{ profileGuidSettingsMap.find(profile.Guid()) };
@@ -3228,7 +3249,7 @@ namespace winrt::TerminalApp::implementation
{
pair.second = TerminalSettings::CreateWithProfile(_settings, pair.first, *_bindings);
}
pane->UpdateSettings(pair.second, pair.first);
pane->UpdateTerminalSettings(pair.second, pair.first);
}
}
});
@@ -3809,7 +3830,10 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::Microsoft::Terminal::Settings::Editor::MainPage sui{ _settings };
// Create the SUI pane content
auto settingsContent{ winrt::make_self<SettingsPaneContent>(_settings) };
auto sui = settingsContent->SettingsUI();
if (_hostingHwnd)
{
sui.SetHostingWindow(reinterpret_cast<uint64_t>(*_hostingHwnd));
@@ -3825,54 +3849,68 @@ namespace winrt::TerminalApp::implementation
}
});
auto newTabImpl = winrt::make_self<SettingsTab>(sui, _settings.GlobalSettings().CurrentTheme().RequestedTheme());
// Create the tab
auto resultPane = std::make_shared<Pane>(*settingsContent);
_settingsTab = _CreateNewTabFromPane(resultPane);
// Add the new tab to the list of our tabs.
_tabs.Append(*newTabImpl);
_mruTabs.Append(*newTabImpl);
// auto newTabImpl = winrt::make_self<SettingsTab>(sui, _settings.GlobalSettings().CurrentTheme().RequestedTheme());
newTabImpl->SetDispatch(*_actionDispatch);
newTabImpl->SetActionMap(_settings.ActionMap());
// // Add the new tab to the list of our tabs.
// _tabs.Append(*newTabImpl);
// _mruTabs.Append(*newTabImpl);
// Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
_UpdateTabIndices();
// newTabImpl->SetDispatch(*_actionDispatch);
// newTabImpl->SetActionMap(_settings.ActionMap());
// Don't capture a strong ref to the tab. If the tab is removed as this
// is called, we don't really care anymore about handling the event.
auto weakTab = make_weak(newTabImpl);
// // Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
// _UpdateTabIndices();
auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem);
// // Don't capture a strong ref to the tab. If the tab is removed as this
// // is called, we don't really care anymore about handling the event.
// auto weakTab = make_weak(newTabImpl);
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
// auto tabViewItem = newTabImpl->TabViewItem();
// _tabView.TabItems().Append(tabViewItem);
// When the tab requests close, try to close it (prompt for approval, if required)
newTabImpl->CloseRequested([weakTab, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
// tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
if (page && tab)
{
page->_HandleCloseTabRequested(*tab);
}
});
// // When the tab requests close, try to close it (prompt for approval, if required)
// newTabImpl->CloseRequested([weakTab, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
// auto page{ weakThis.get() };
// auto tab{ weakTab.get() };
// if (page && tab)
// {
// page->_HandleCloseTabRequested(*tab);
// }
// });
// When the tab is closed, remove it from our list of tabs.
newTabImpl->Closed([weakTab, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
const auto page = weakThis.get();
const auto tab = weakTab.get();
// TODO! Make sure we remove the _settingsTab if it is closed!
// ---------------------- main
// newTabImpl->Closed([weakTab, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
// const auto page = weakThis.get();
// const auto tab = weakTab.get();
if (page && tab)
{
page->_RemoveTab(*tab);
}
});
// if (page && tab)
// {
// page->_RemoveTab(*tab);
// }
// });
// =======
// // When the tab is closed, remove it from our list of tabs.
// newTabImpl->Closed([tabViewItem, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
// if (auto page{ weakThis.get() })
// {
// page->_settingsTab = nullptr;
// page->_RemoveOnCloseRoutine(tabViewItem, page);
// }
// });
// -------------------------- dev/migrie/fhl/scratchpad-pane
_settingsTab = *newTabImpl;
// _settingsTab = *newTabImpl;
// This kicks off TabView::SelectionChanged, in response to which
// we'll attach the terminal's Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem);
//// This kicks off TabView::SelectionChanged, in response to which
//// we'll attach the terminal's Xaml control to the Xaml root.
//_tabView.SelectedItem(tabViewItem);
}
else
{

View File

@@ -223,7 +223,7 @@ namespace winrt::TerminalApp::implementation
void _UpdateTabIndices();
TerminalApp::SettingsTab _settingsTab{ nullptr };
TerminalApp::TerminalTab _settingsTab{ nullptr };
bool _isInFocusMode{ false };
bool _isFullscreen{ false };
@@ -301,14 +301,14 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition = -1);
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs);
TerminalApp::TerminalTab _CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition = -1);
std::wstring _evaluatePathForCwd(std::wstring_view path);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings, const bool inheritCursor);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(std::shared_ptr<Pane> pane);
void _restartPaneConnection(const std::shared_ptr<Pane>& pane);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent);
void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&);
winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);

View File

@@ -0,0 +1,332 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TerminalPaneContent.h"
#include "PaneArgs.h"
#include "TerminalPaneContent.g.cpp"
#include <Mmsystem.h>
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
namespace winrt::TerminalApp::implementation
{
TerminalPaneContent::TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const winrt::Microsoft::Terminal::Control::TermControl& control) :
_control{ control },
_profile{ profile }
{
_setupControlEvents();
}
void TerminalPaneContent::_setupControlEvents()
{
_controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &TerminalPaneContent::_ControlConnectionStateChangedHandler });
_controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_ControlWarningBellHandler });
_controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_CloseTerminalRequestedHandler });
_controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_RestartTerminalRequestedHandler });
_controlEvents._TitleChanged = _control.TitleChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTitleChanged });
_controlEvents._TabColorChanged = _control.TabColorChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTabColorChanged });
_controlEvents._SetTaskbarProgress = _control.SetTaskbarProgress(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlSetTaskbarProgress });
_controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlReadOnlyChanged });
_controlEvents._FocusFollowMouseRequested = _control.FocusFollowMouseRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlFocusFollowMouseRequested });
}
void TerminalPaneContent::_removeControlEvents()
{
_controlEvents = {};
}
winrt::Windows::UI::Xaml::FrameworkElement TerminalPaneContent::GetRoot()
{
return _control;
}
winrt::Microsoft::Terminal::Control::TermControl TerminalPaneContent::GetTerminal()
{
return _control;
}
winrt::Windows::Foundation::Size TerminalPaneContent::MinSize()
{
return _control.MinimumSize();
}
void TerminalPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason)
{
_control.Focus(reason);
}
void TerminalPaneContent::Close()
{
_removeControlEvents();
// Clear out our media player callbacks, and stop any playing media. This
// will prevent the callback from being triggered after we've closed, and
// also make sure that our sound stops when we're closed.
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
_bellPlayer = nullptr;
_bellPlayerCreated = false;
}
CloseRequested.raise(*this, nullptr);
}
winrt::hstring TerminalPaneContent::Icon() const
{
return _profile.Icon();
}
Windows::Foundation::IReference<winrt::Windows::UI::Color> TerminalPaneContent::TabColor() const noexcept
{
return _control.TabColor();
}
NewTerminalArgs TerminalPaneContent::GetNewTerminalArgs(const bool asContent) const
{
NewTerminalArgs args{};
auto controlSettings = _control.Settings();
args.Profile(controlSettings.ProfileName());
// If we know the user's working directory use it instead of the profile.
if (const auto dir = _control.WorkingDirectory(); !dir.empty())
{
args.StartingDirectory(dir);
}
else
{
args.StartingDirectory(controlSettings.StartingDirectory());
}
args.TabTitle(controlSettings.StartingTitle());
args.Commandline(controlSettings.Commandline());
args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle());
if (controlSettings.TabColor() || controlSettings.StartingTabColor())
{
til::color c;
// StartingTabColor is prioritized over other colors
if (const auto color = controlSettings.StartingTabColor())
{
c = til::color(color.Value());
}
else
{
c = til::color(controlSettings.TabColor().Value());
}
args.TabColor(winrt::Windows::Foundation::IReference<winrt::Windows::UI::Color>{ static_cast<winrt::Windows::UI::Color>(c) });
}
// TODO:GH#9800 - we used to be able to persist the color scheme that a
// TermControl was initialized with, by name. With the change to having the
// control own its own copy of its settings, this isn't possible anymore.
//
// We may be able to get around this by storing the Name in the Core::Scheme
// object. That would work for schemes set by the Terminal, but not ones set
// by VT, but that seems good enough.
// Only fill in the ContentId if absolutely needed. If you fill in a number
// here (even 0), we'll serialize that number, AND treat that action as an
// "attach existing" rather than a "create"
if (asContent)
{
args.ContentId(_control.ContentId());
}
return args;
}
void TerminalPaneContent::_controlTitleChanged(const IInspectable&, const IInspectable&)
{
TitleChanged.raise(*this, nullptr);
}
void TerminalPaneContent::_controlTabColorChanged(const IInspectable&, const IInspectable&)
{
TabColorChanged.raise(*this, nullptr);
}
void TerminalPaneContent::_controlSetTaskbarProgress(const IInspectable&, const IInspectable&)
{
TaskbarProgressChanged.raise(*this, nullptr);
}
void TerminalPaneContent::_controlReadOnlyChanged(const IInspectable&, const IInspectable&)
{
ReadOnlyChanged.raise(*this, nullptr);
}
void TerminalPaneContent::_controlFocusFollowMouseRequested(const IInspectable&, const IInspectable&)
{
FocusRequested.raise(*this, nullptr);
}
// Method Description:
// - Called when our attached control is closed. Triggers listeners to our close
// event, if we're a leaf pane.
// - If this was called, and we became a parent pane (due to work on another
// thread), this function will do nothing (allowing the control's new parent
// to handle the event instead).
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalPaneContent::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
const auto newConnectionState = _control.ConnectionState();
const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
if (newConnectionState < ConnectionState::Closed)
{
// Pane doesn't care if the connection isn't entering a terminal state.
return;
}
if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed)
{
// A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit".
// This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting
// in Terminal flashing open and immediately closed.
return;
}
if (_profile)
{
if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic)
{
// For 'automatic', we only care about the connection state if we were launched by Terminal
// Since we were launched via defterm, ignore the connection state (i.e. we treat the
// close on exit mode as 'always', see GH #13325 for discussion)
Close();
}
const auto mode = _profile.CloseOnExit();
if ((mode == CloseOnExitMode::Always) ||
((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed))
{
Close();
}
}
}
// Method Description:
// - Plays a warning note when triggered by the BEL control character,
// using the sound configured for the "Critical Stop" system event.`
// This matches the behavior of the Windows Console host.
// - Will also flash the taskbar if the bellStyle setting for this profile
// has the 'visual' flag set
// Arguments:
// - <unused>
void TerminalPaneContent::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
{
if (_profile)
{
// We don't want to do anything if nothing is set, so check for that first
if (static_cast<int>(_profile.BellStyle()) != 0)
{
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{ wil::ExpandEnvironmentStringsW<std::wstring>(sounds.GetAt(rand() % sounds.Size()).c_str()) };
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);
}
}
if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window))
{
_control.BellLightOn();
}
// raise the event with the bool value corresponding to the taskbar flag
BellRequested.raise(*this,
*winrt::make_self<TerminalApp::implementation::BellEventArgs>(WI_IsFlagSet(_profile.BellStyle(), BellStyle::Taskbar)));
}
}
}
winrt::fire_and_forget TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(_control.Dispatcher());
if (auto pane{ weakThis.get() })
{
if (!_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayerCreated = true;
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
if (_bellPlayer)
{
const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) };
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
_bellPlayer.Source(item);
_bellPlayer.Play();
}
}
}
void TerminalPaneContent::_CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
Close();
}
void TerminalPaneContent::_RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
RestartTerminalRequested.raise(*this, nullptr);
}
void TerminalPaneContent::UpdateSettings(const CascadiaSettings& /*settings*/)
{
// Do nothing. We'll later be updated manually by
// UpdateTerminalSettings, which we need for profile and
// focused/unfocused settings.
}
void TerminalPaneContent::UpdateTerminalSettings(const TerminalSettingsCreateResult& settings,
const Profile& profile)
{
_profile = profile;
_control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings());
}
// Method Description:
// - Should be called when this pane is created via a default terminal handoff
// - Finalizes our configuration given the information that we have been
// created via default handoff
void TerminalPaneContent::MarkAsDefterm()
{
_isDefTermSession = true;
}
winrt::Windows::UI::Xaml::Media::Brush TerminalPaneContent::BackgroundBrush()
{
return _control.BackgroundBrush();
}
float TerminalPaneContent::SnapDownToGrid(const TerminalApp::PaneSnapDirection direction, const float sizeToSnap)
{
return _control.SnapDimensionToGrid(direction == PaneSnapDirection::Width, sizeToSnap);
}
Windows::Foundation::Size TerminalPaneContent::GridSize()
{
return _control.CharacterDimensions();
}
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "TerminalPaneContent.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include <til/winrt.h>
namespace winrt::TerminalApp::implementation
{
struct TerminalPaneContent : TerminalPaneContentT<TerminalPaneContent>
{
TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const winrt::Microsoft::Terminal::Control::TermControl& control);
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
winrt::Microsoft::Terminal::Control::TermControl GetTerminal();
winrt::Windows::Foundation::Size MinSize();
void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic);
void Close();
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const;
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
void UpdateTerminalSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
void MarkAsDefterm();
winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const
{
return _profile;
};
winrt::hstring Title() { return _control.Title(); }
uint64_t TaskbarState() { return _control.TaskbarState(); }
uint64_t TaskbarProgress() { return _control.TaskbarProgress(); }
bool ReadOnly() { return _control.ReadOnly(); }
winrt::hstring Icon() const;
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() const noexcept;
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
float SnapDownToGrid(const TerminalApp::PaneSnapDirection direction, const float sizeToSnap);
Windows::Foundation::Size GridSize();
til::typed_event<TerminalApp::TerminalPaneContent, winrt::Windows::Foundation::IInspectable> RestartTerminalRequested;
til::typed_event<> CloseRequested;
til::typed_event<winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::BellEventArgs> BellRequested;
til::typed_event<> TitleChanged;
til::typed_event<> TabColorChanged;
til::typed_event<> TaskbarProgressChanged;
til::typed_event<> ReadOnlyChanged;
til::typed_event<> FocusRequested;
private:
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
bool _isDefTermSession{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
bool _bellPlayerCreated{ false };
struct ControlEventTokens
{
winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged;
winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell;
winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested;
winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested;
winrt::Microsoft::Terminal::Control::TermControl::TitleChanged_revoker _TitleChanged;
winrt::Microsoft::Terminal::Control::TermControl::TabColorChanged_revoker _TabColorChanged;
winrt::Microsoft::Terminal::Control::TermControl::SetTaskbarProgress_revoker _SetTaskbarProgress;
winrt::Microsoft::Terminal::Control::TermControl::ReadOnlyChanged_revoker _ReadOnlyChanged;
winrt::Microsoft::Terminal::Control::TermControl::FocusFollowMouseRequested_revoker _FocusFollowMouseRequested;
} _controlEvents;
void _setupControlEvents();
void _removeControlEvents();
winrt::fire_and_forget _playBellSound(winrt::Windows::Foundation::Uri uri);
void _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 _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);
void _controlTabColorChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
void _controlSetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
void _controlReadOnlyChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
void _controlFocusFollowMouseRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
};
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "IPaneContent.idl";
namespace TerminalApp
{
[default_interface] runtimeclass TerminalPaneContent : IPaneContent, ISnappable
{
Microsoft.Terminal.Control.TermControl GetTerminal();
void UpdateTerminalSettings(Microsoft.Terminal.Settings.Model.TerminalSettingsCreateResult settings,
Microsoft.Terminal.Settings.Model.Profile profile);
void MarkAsDefterm();
Microsoft.Terminal.Settings.Model.Profile GetProfile();
event Windows.Foundation.TypedEventHandler<TerminalPaneContent, Object> RestartTerminalRequested;
}
[default_interface] runtimeclass ScratchpadContent : IPaneContent
{
}
[default_interface] runtimeclass SettingsPaneContent : IPaneContent
{
SettingsPaneContent(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
}
[default_interface] runtimeclass OceanContent : IPaneContent
{
// Let's just smuggle the HWND in as a UInt64 :|
void SetHostingWindow(UInt64 window);
}
}

View File

@@ -189,6 +189,11 @@ namespace winrt::TerminalApp::implementation
return nullptr;
}
IPaneContent TerminalTab::GetActiveContent() const
{
return _activePane ? _activePane->GetContent() : nullptr;
}
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl objects
@@ -203,9 +208,9 @@ namespace winrt::TerminalApp::implementation
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
// Attach event handlers to each new pane
_AttachEventHandlersToPane(pane);
if (auto control = pane->GetTerminalControl())
if (auto content = pane->GetContent())
{
_AttachEventHandlersToControl(pane->Id().value(), control);
_AttachEventHandlersToContent(pane->Id().value(), content);
}
});
}
@@ -264,12 +269,18 @@ namespace winrt::TerminalApp::implementation
// of the settings that apply to all tabs.
// Return Value:
// - <none>
void TerminalTab::UpdateSettings()
void TerminalTab::UpdateSettings(const CascadiaSettings& settings)
{
ASSERT_UI_THREAD();
// The tabWidthMode may have changed, update the header control accordingly
_UpdateHeaderControlMaxWidth();
// Update the settings on all our panes.
_rootPane->WalkTree([&](auto pane) {
pane->UpdateSettings(settings);
return false;
});
}
// Method Description:
@@ -278,7 +289,7 @@ namespace winrt::TerminalApp::implementation
// - iconPath: The new path string to use as the IconPath for our TabViewItem
// Return Value:
// - <none>
void TerminalTab::UpdateIcon(const winrt::hstring iconPath)
void TerminalTab::UpdateIcon(const winrt::hstring& iconPath)
{
ASSERT_UI_THREAD();
@@ -372,8 +383,8 @@ namespace winrt::TerminalApp::implementation
{
return RS_(L"MultiplePanes");
}
const auto lastFocusedControl = GetActiveTerminalControl();
return lastFocusedControl ? lastFocusedControl.Title() : L"";
const auto activeContent = GetActiveContent();
return activeContent ? activeContent.Title() : winrt::hstring{ L"" };
}
// Method Description:
@@ -508,7 +519,10 @@ namespace winrt::TerminalApp::implementation
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
_AttachEventHandlersToControl(p->Id().value(), p->_control);
if (const auto& content{ p->GetContent() })
{
_AttachEventHandlersToContent(p->Id().value(), content);
}
_nextPaneId++;
}
return false;
@@ -619,7 +633,11 @@ namespace winrt::TerminalApp::implementation
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
_AttachEventHandlersToControl(p->Id().value(), p->_control);
if (const auto& content{ p->GetContent() })
{
_AttachEventHandlersToContent(p->Id().value(), content);
}
_nextPaneId++;
}
});
@@ -876,28 +894,18 @@ namespace winrt::TerminalApp::implementation
// the control itself doesn't have a particular ID and its pointer is
// unstable since it is moved when panes split.
// Arguments:
// - paneId: The ID of the pane that contains the given control.
// - control: the control to remove events from.
// - paneId: The ID of the pane that contains the given content.
// Return Value:
// - <none>
void TerminalTab::_DetachEventHandlersFromControl(const uint32_t paneId, const TermControl& control)
void TerminalTab::_DetachEventHandlersFromContent(const uint32_t paneId)
{
auto it = _controlEvents.find(paneId);
if (it != _controlEvents.end())
auto it = _contentEvents.find(paneId);
if (it != _contentEvents.end())
{
auto& events = it->second;
events = {};
control.TitleChanged(events.titleToken);
control.TabColorChanged(events.colorToken);
control.SetTaskbarProgress(events.taskbarToken);
control.ReadOnlyChanged(events.readOnlyToken);
control.FocusFollowMouseRequested(events.focusToken);
events.KeySent.revoke();
events.CharSent.revoke();
events.StringSent.revoke();
_controlEvents.erase(paneId);
_contentEvents.erase(paneId);
}
}
@@ -912,71 +920,84 @@ namespace winrt::TerminalApp::implementation
// - control: the TermControl to add events to.
// Return Value:
// - <none>
void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control)
void TerminalTab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content)
{
auto weakThis{ get_weak() };
auto dispatcher = TabViewItem().Dispatcher();
ControlEventTokens events{};
ContentEventTokens events{};
events.titleToken = control.TitleChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
// The title of the control changed, but not necessarily the title of the tab.
// Set the tab's text to the active panes' text.
tab->UpdateTitle();
}
});
events.colorToken = control.TabColorChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (auto tab{ weakThis.get() })
{
// The control's tabColor changed, but it is not necessarily the
// active control in this tab. We'll just recalculate the
// current color anyways.
tab->_RecalculateAndApplyTabColor();
}
});
events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
tab->_UpdateProgressState();
}
});
events.readOnlyToken = control.ReadOnlyChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (auto tab{ weakThis.get() })
{
tab->_RecalculateAndApplyReadOnly();
}
});
events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (const auto tab{ weakThis.get() })
{
if (tab->_focused())
events.TitleChanged = content.TitleChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
if (const auto termControl{ sender.try_as<winrt::Microsoft::Terminal::Control::TermControl>() })
// The title of the control changed, but not necessarily the title of the tab.
// Set the tab's text to the active panes' text.
tab->UpdateTitle();
}
});
events.TabColorChanged = content.TabColorChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (auto tab{ weakThis.get() })
{
// The control's tabColor changed, but it is not necessarily the
// active control in this tab. We'll just recalculate the
// current color anyways.
tab->_RecalculateAndApplyTabColor();
}
});
events.TaskbarProgressChanged = content.TaskbarProgressChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
tab->_UpdateProgressState();
}
});
events.ReadOnlyChanged = content.ReadOnlyChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (auto tab{ weakThis.get() })
{
tab->_RecalculateAndApplyReadOnly();
}
});
events.FocusRequested = content.FocusRequested(
winrt::auto_revoke,
[dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget {
co_await wil::resume_foreground(dispatcher);
if (const auto tab{ weakThis.get() })
{
if (tab->_focused())
{
termControl.Focus(FocusState::Pointer);
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
{
content.Focus(FocusState::Pointer);
}
}
}
}
});
});
if (_tabStatus.IsInputBroadcastActive())
{
_addBroadcastHandlers(control, events);
if (const auto& termContent{ content.try_as<TerminalApp::TerminalPaneContent>() })
{
_addBroadcastHandlers(termContent.GetTerminal(), events);
}
}
_controlEvents[paneId] = std::move(events);
_contentEvents[paneId] = std::move(events);
}
// Method Description:
@@ -1191,36 +1212,12 @@ namespace winrt::TerminalApp::implementation
}
});
// Add a PaneRaiseBell event handler to the Pane
auto bellToken = pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
if (auto tab{ weakThis.get() })
{
if (visual)
{
// If visual is set, we need to bubble this event all the way to app host to flash the taskbar
// In this part of the chain we bubble it from the hosting tab to the page
tab->_TabRaiseVisualBellHandlers();
}
// Show the bell indicator in the tab header
tab->ShowBellIndicator(true);
// If this tab is focused, activate the bell indicator timer, which will
// remove the bell indicator once it fires
// (otherwise, the indicator is removed when the tab gets focus)
if (tab->_focusState != WUX::FocusState::Unfocused)
{
tab->ActivateBellIndicatorTimer();
}
}
});
// box the event token so that we can give a reference to it in the
// event handler.
auto detachedToken = std::make_shared<winrt::event_token>();
// Add a Detached event handler to the Pane to clean up tab state
// and other event handlers when a pane is removed from this tab.
*detachedToken = pane->Detached([weakThis, weakPane, gotFocusToken, lostFocusToken, closedToken, bellToken, detachedToken](std::shared_ptr<Pane> /*sender*/) {
*detachedToken = pane->Detached([weakThis, weakPane, gotFocusToken, lostFocusToken, closedToken, detachedToken](std::shared_ptr<Pane> /*sender*/) {
// Make sure we do this at most once
if (auto pane{ weakPane.lock() })
{
@@ -1228,14 +1225,10 @@ namespace winrt::TerminalApp::implementation
pane->GotFocus(gotFocusToken);
pane->LostFocus(lostFocusToken);
pane->Closed(closedToken);
pane->PaneRaiseBell(bellToken);
if (auto tab{ weakThis.get() })
{
if (auto control = pane->GetTerminalControl())
{
tab->_DetachEventHandlersFromControl(pane->Id().value(), control);
}
tab->_DetachEventHandlersFromContent(pane->Id().value());
for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i)
{
@@ -1413,7 +1406,12 @@ namespace winrt::TerminalApp::implementation
// GH#10112 - if we're opening the tab renamer, don't
// immediately toss focus to the control. We don't want to steal
// focus from the tab renamer.
if (!tab->_headerControl.InRename() && !tab->GetActiveTerminalControl().SearchBoxEditInFocus())
const auto& terminalControl{ tab->GetActiveTerminalControl() }; // maybe null
// If we're
// * NOT in a rename
// * AND (the content isn't a TermControl, OR the term control doesn't have focus in the search box)
if (!tab->_headerControl.InRename() &&
(terminalControl == nullptr || !terminalControl.SearchBoxEditInFocus()))
{
tab->_RequestFocusActiveControlHandlers();
}
@@ -1435,12 +1433,12 @@ namespace winrt::TerminalApp::implementation
{
ASSERT_UI_THREAD();
std::optional<winrt::Windows::UI::Color> controlTabColor;
if (const auto& control = GetActiveTerminalControl())
std::optional<winrt::Windows::UI::Color> contentTabColor;
if (const auto& content{ GetActiveContent() })
{
if (const auto color = control.TabColor())
if (const auto color = content.TabColor())
{
controlTabColor = color.Value();
contentTabColor = color.Value();
}
}
@@ -1450,7 +1448,7 @@ namespace winrt::TerminalApp::implementation
// Color | | Set by
// -------------------- | -- | --
// Runtime Color | _optional_ | Color Picker / `setTabColor` action
// Control Tab Color | _optional_ | Profile's `tabColor`, or a color set by VT
// Content Tab Color | _optional_ | Profile's `tabColor`, or a color set by VT (whatever the tab's content wants)
// Theme Tab Background | _optional_ | `tab.backgroundColor` in the theme (handled in _RecalculateAndApplyTabColor)
// Tab Default Color | **default** | TabView in XAML
//
@@ -1459,7 +1457,7 @@ namespace winrt::TerminalApp::implementation
// tabview color" (and clear out any colors we've set).
return til::coalesce(_runtimeTabColor,
controlTabColor,
contentTabColor,
std::optional<Windows::UI::Color>(std::nullopt));
}
@@ -1498,7 +1496,7 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::Media::Brush TerminalTab::_BackgroundBrush()
{
Media::Brush terminalBrush{ nullptr };
if (const auto& c{ GetActiveTerminalControl() })
if (const auto& c{ GetActiveContent() })
{
terminalBrush = c.BackgroundBrush();
}
@@ -1694,6 +1692,10 @@ namespace winrt::TerminalApp::implementation
ReadOnly(_rootPane->ContainsReadOnly());
_updateIsClosable();
// Update all the visuals on all our panes, so they can update their
// border colors accordingly.
_rootPane->WalkTree([](const auto& p) { p->UpdateVisuals(); });
}
std::shared_ptr<Pane> TerminalTab::GetActivePane() const
@@ -1746,8 +1748,8 @@ namespace winrt::TerminalApp::implementation
}
if (const auto& control{ p->GetTerminalControl() })
{
auto it = _controlEvents.find(*paneId);
if (it != _controlEvents.end())
auto it = _contentEvents.find(*paneId);
if (it != _contentEvents.end())
{
auto& events = it->second;
@@ -1765,7 +1767,7 @@ namespace winrt::TerminalApp::implementation
});
}
void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ControlEventTokens& events)
void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ContentEventTokens& events)
{
auto weakThis{ get_weak() };
// ADD EVENT HANDLERS HERE

View File

@@ -25,6 +25,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Control::TermControl GetActiveTerminalControl() const;
winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile() const noexcept;
winrt::TerminalApp::IPaneContent GetActiveContent() const;
void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
@@ -41,7 +42,7 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Pane> newPane);
void ToggleSplitOrientation();
void UpdateIcon(const winrt::hstring iconPath);
void UpdateIcon(const winrt::hstring& iconPath);
void HideIcon(const bool hide);
void ShowBellIndicator(const bool show);
@@ -57,7 +58,7 @@ namespace winrt::TerminalApp::implementation
bool SwapPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool FocusPane(const uint32_t id);
void UpdateSettings();
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
void UpdateTitle();
void Shutdown() override;
@@ -120,19 +121,21 @@ namespace winrt::TerminalApp::implementation
winrt::event_token _colorClearedToken;
winrt::event_token _pickerClosedToken;
struct ControlEventTokens
struct ContentEventTokens
{
winrt::event_token titleToken;
winrt::event_token colorToken;
winrt::event_token taskbarToken;
winrt::event_token readOnlyToken;
winrt::event_token focusToken;
winrt::TerminalApp::IPaneContent::BellRequested_revoker BellRequested;
winrt::TerminalApp::IPaneContent::TitleChanged_revoker TitleChanged;
winrt::TerminalApp::IPaneContent::TabColorChanged_revoker TabColorChanged;
winrt::TerminalApp::IPaneContent::TaskbarProgressChanged_revoker TaskbarProgressChanged;
winrt::TerminalApp::IPaneContent::ReadOnlyChanged_revoker ReadOnlyChanged;
winrt::TerminalApp::IPaneContent::FocusRequested_revoker FocusRequested;
// These events literally only apply if the content is a TermControl.
winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent;
winrt::Microsoft::Terminal::Control::TermControl::CharSent_revoker CharSent;
winrt::Microsoft::Terminal::Control::TermControl::StringSent_revoker StringSent;
};
std::unordered_map<uint32_t, ControlEventTokens> _controlEvents;
std::unordered_map<uint32_t, ContentEventTokens> _contentEvents;
winrt::event_token _rootClosedToken{};
@@ -159,8 +162,8 @@ namespace winrt::TerminalApp::implementation
void _CreateContextMenu() override;
virtual winrt::hstring _CreateToolTipTitle() override;
void _DetachEventHandlersFromControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
void _AttachEventHandlersToControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
void _DetachEventHandlersFromContent(const uint32_t paneId);
void _AttachEventHandlersToContent(const uint32_t paneId, const winrt::TerminalApp::IPaneContent& content);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
void _UpdateActivePane(std::shared_ptr<Pane> pane);
@@ -175,7 +178,7 @@ namespace winrt::TerminalApp::implementation
virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override;
void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ControlEventTokens& events);
void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ContentEventTokens& events);
void _chooseColorClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _renameTabClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);

View File

@@ -81,6 +81,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <til/winrt.h>
#include <cppwinrt_utils.h>
#include <wil/cppwinrt_helpers.h> // must go after the CoreDispatcher type is defined

View File

@@ -96,6 +96,7 @@ static constexpr std::string_view ShowContextMenuKey{ "showContextMenu" };
static constexpr std::string_view ExpandSelectionToWordKey{ "expandSelectionToWord" };
static constexpr std::string_view RestartConnectionKey{ "restartConnection" };
static constexpr std::string_view ToggleBroadcastInputKey{ "toggleBroadcastInput" };
static constexpr std::string_view OpenScratchpadKey{ "experimental.openScratchpad" };
static constexpr std::string_view ActionKey{ "action" };
@@ -430,6 +431,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::ExpandSelectionToWord, RS_(L"ExpandSelectionToWordCommandKey") },
{ ShortcutAction::RestartConnection, RS_(L"RestartConnectionKey") },
{ ShortcutAction::ToggleBroadcastInput, RS_(L"ToggleBroadcastInputCommandKey") },
{ ShortcutAction::OpenScratchpad, RS_(L"OpenScratchpadKey") },
};
}();

View File

@@ -109,7 +109,8 @@
ON_ALL_ACTIONS(ExpandSelectionToWord) \
ON_ALL_ACTIONS(CloseOtherPanes) \
ON_ALL_ACTIONS(RestartConnection) \
ON_ALL_ACTIONS(ToggleBroadcastInput)
ON_ALL_ACTIONS(ToggleBroadcastInput) \
ON_ALL_ACTIONS(OpenScratchpad)
#define ALL_SHORTCUT_ACTIONS_WITH_ARGS \
ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -700,6 +700,9 @@
<data name="RestartConnectionKey" xml:space="preserve">
<value>Restart connection</value>
</data>
<data name="OpenScratchpadKey" xml:space="preserve">
<value>Open scratchpad</value>
</data>
<data name="SelectOutputNextCommandKey" xml:space="preserve">
<value>Select next command output</value>
</data>

View File

@@ -122,7 +122,7 @@ void IslandWindow::MakeWindow() noexcept
WINRT_VERIFY(CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP | (_alwaysOnTop ? WS_EX_TOPMOST : 0),
wc.lpszClassName,
L"Windows Terminal",
WS_OVERLAPPEDWINDOW,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,

View File

@@ -187,4 +187,14 @@
</alwaysEnabledBrandingTokens>
</feature>
<feature>
<name>Feature_ScratchpadPane</name>
<description>Allow the user to create scratchpad panes. Mostly just exists to validate non-terminal panes.</description>
<id>997</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
</featureStaging>

View File

@@ -72,7 +72,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
winrt::event<ArgsT> _handlers;
};
template<typename SenderT, typename ArgsT>
template<typename SenderT = winrt::Windows::Foundation::IInspectable, typename ArgsT = winrt::Windows::Foundation::IInspectable>
struct typed_event
{
typed_event<SenderT, ArgsT>() = default;

View File

@@ -0,0 +1,50 @@
#include <windows.h>
#include <string>
#include "ExtensionInterface.h"
static HBRUSH s_magentaBrush;
LRESULT CALLBACK ExtensionWindowProc(HWND hWnd,
UINT Message,
WPARAM wParam,
LPARAM lParam)
{
wParam;
lParam;
switch (Message)
{
case WM_CREATE:
{
wprintf(L"WM_CREATE\n");
// Create a magenta brush for later
s_magentaBrush = CreateSolidBrush(RGB(255, 0, 255));
break;
}
// case WM_SIZE:
// {
// const auto width = LOWORD(lParam);
// const auto height = HIWORD(lParam);
// wprintf(L"resized to: %d, %d\n", width, height);
// break;
// }
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, s_magentaBrush);
EndPaint(hWnd, &ps);
break;
}
}
return 0;
}
// TODO! hax for now
void StartExtension()
{
(void)SetExtensionWindowProc(ExtensionWindowProc);
}

View File

@@ -0,0 +1,9 @@
#pragma once
// For real code we'd do something like this
// HWND CreateExtensionWindow();
// But for now wwell just call main()
// Set the window procedure that the extension host library should call to.
// This lets the extension implementer handle window messages too.
void SetExtensionWindowProc(WNDPROC proc);

View File

@@ -11,6 +11,7 @@
<Import Project="..\..\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="ExtensionCode.cpp" />
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>

View File

@@ -2,9 +2,338 @@
// Licensed under the MIT license.
#include <windows.h>
#include <string>
#include "ExtensionInterface.h"
// Used by window structures to place our special frozen-console painting data
#define GWL_CONSOLE_WNDALLOC (3 * sizeof(DWORD))
HANDLE g_stdOut{ nullptr };
LRESULT CALLBACK s_ExtensionWindowProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK _ourExtensionWindowProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);
// TODO! hax for now
void StartExtension();
HWND g_parent;
HWND g_child;
HWND g_extension;
WNDPROC g_extensionProc{ nullptr };
LRESULT CALLBACK s_ScratchWindowProc(HWND hWnd,
UINT Message,
WPARAM wParam,
LPARAM lParam)
{
switch (Message)
{
case WM_CREATE:
{
// Show the window
ShowWindow(hWnd, SW_SHOWDEFAULT);
// // Make the window a topmost window cause islands are weird
// SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// Nope, that didn't work
break;
}
case WM_SIZE:
{
const auto width = LOWORD(lParam);
const auto height = HIWORD(lParam);
wprintf(L"resized to: %d, %d\n", width, height);
// Resize the g_extension to match
SetWindowPos(g_extension, nullptr, 0, 0, width, height, SWP_NOZORDER);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_HIGHLIGHT + 1));
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
// If we get this far, call the default window proc
return DefWindowProcW(hWnd, Message, wParam, lParam);
}
void resizeChildToMatchParent(HWND parent, HWND child)
{
// get the position of the parent window
RECT rect;
GetWindowRect(parent, &rect);
// resize the child window to match
// (leave a 48px margin around the child window, for debug)
SetWindowPos(child,
nullptr,
rect.left + 48,
rect.top + 48,
rect.right - rect.left - 96,
rect.bottom - rect.top - 96,
/* SWP_NOZORDER | */ SWP_NOACTIVATE);
}
void setupHooks(HWND parent, HWND child)
{
// get pid and tid for parent window
DWORD pid = 0;
DWORD tid = GetWindowThreadProcessId(parent, &pid);
g_parent = parent;
g_child = child;
// Ala
// https://devblogs.microsoft.com/oldnewthing/20210104-00/?p=104656
// circa 2021
WINEVENTPROC sizeChange = [](HWINEVENTHOOK /* hWinEventHook */,
DWORD /* event */,
HWND hwnd,
LONG /* idObject */,
LONG /* idChild */,
DWORD /* dwEventThread */,
DWORD /* dwmsEventTime */) {
if (hwnd == g_parent)
{
// wprintf(L"Got a location change\n");
resizeChildToMatchParent(g_parent, g_child);
}
};
// Listen for changes in size and pos of the ownerhandle
SetWinEventHook(
EVENT_OBJECT_LOCATIONCHANGE,
EVENT_OBJECT_LOCATIONCHANGE,
nullptr,
reinterpret_cast<WINEVENTPROC>(sizeChange),
pid,
tid,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
// Listen for changes in the ownerhandle's visibility
SetWinEventHook(
EVENT_OBJECT_SHOW,
EVENT_OBJECT_HIDE,
nullptr,
[](HWINEVENTHOOK /* hWinEventHook */,
DWORD /* event */,
HWND /* hwnd */,
LONG /* idObject */,
LONG /* idChild */,
DWORD /* dwEventThread */,
DWORD /* dwmsEventTime */) {
// if (hwnd == g_hWnd)
// {
// wprintf(L"Got a visibility change\n");
// }
},
pid,
tid,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
SetWinEventHook(
EVENT_OBJECT_REORDER,
EVENT_OBJECT_REORDER,
nullptr,
[](HWINEVENTHOOK /* hWinEventHook */,
DWORD /* event */,
HWND hwnd,
LONG /* idObject */,
LONG /* idChild */,
DWORD /* dwEventThread */,
DWORD /* dwmsEventTime */) {
// n.b. not sure this is ever actually called
if (hwnd == g_parent)
{
wprintf(L"Got a reorder change\n");
// just bring us to the top
SetWindowPos(g_child, g_parent /* HWND_TOP */, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
},
pid,
tid,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
SetWinEventHook(
EVENT_OBJECT_DESTROY,
EVENT_OBJECT_DESTROY,
nullptr,
[](HWINEVENTHOOK /* hWinEventHook */,
DWORD /* event */,
HWND /* hwnd */,
LONG /* idObject */,
LONG /* idChild */,
DWORD /* dwEventThread */,
DWORD /* dwmsEventTime */) {
// if (hwnd == g_hWnd)
// {
// wprintf(L"Got a destroy change\n");
// }
},
pid,
tid,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
}
// This wmain exists for help in writing scratch programs while debugging.
int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/)
int __cdecl wmain(int argc, WCHAR* argv[])
{
g_stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
// Parse out the handle provided on the command line. It's in hexadecimal.
const auto ownerHandle = argc < 2 ?
0 :
std::stoul(argv[1], nullptr, 16);
wprintf(L"handle: %d\n", ownerHandle);
static const auto SCRATCH_WINDOW_CLASS = L"scratch_window_class";
WNDCLASSEXW scratchClass{ 0 };
scratchClass.cbSize = sizeof(WNDCLASSEXW);
scratchClass.style = CS_HREDRAW | CS_VREDRAW | /*CS_PARENTDC |*/ CS_DBLCLKS;
scratchClass.lpszClassName = SCRATCH_WINDOW_CLASS;
scratchClass.lpfnWndProc = s_ScratchWindowProc;
scratchClass.cbWndExtra = GWL_CONSOLE_WNDALLOC; // this is required to store the owning thread/process override in NTUSER
auto windowClassAtom{ RegisterClassExW(&scratchClass) };
// Create a window
// const auto style = WS_VISIBLE | WS_OVERLAPPEDWINDOW;
const auto style = 0; // WS_VISIBLE | WS_OVERLAPPED; // no resize border, no caption, etc.
HWND hwnd = CreateWindowExW(0, // WS_EX_LAYERED,
reinterpret_cast<LPCWSTR>(windowClassAtom),
L"Hello World",
style,
200, // CW_USEDEFAULT,
200, // CW_USEDEFAULT,
200, // CW_USEDEFAULT,
200, // CW_USEDEFAULT,
0, // owner
nullptr,
nullptr,
nullptr); // gwl
if (hwnd == nullptr)
{
const auto gle = GetLastError();
wprintf(L"Failed to create window: %d\n", gle);
return gle;
}
SetWindowLong(hwnd, GWL_STYLE, 0); //remove all window styles, cause it's created with WS_CAPTION even if we didn't ask for it
ShowWindow(hwnd, SW_SHOW); //display window
// Add hooks so that we can be informed when the child process gets resized.
setupHooks(reinterpret_cast<HWND>(ownerHandle), hwnd);
// Immediately resize to the right size
resizeChildToMatchParent(g_parent, g_child);
// Set our HWND as owned by the parent's. We're always on top of them, but not explicitly a _child_.
::SetWindowLongPtrW(g_child, GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(g_parent));
// Now, create a child window of this first hwnd.
// This is the one that will be the actual extension's HWND.
static const auto EXTN_WINDOW_CLASS = L"my_extension_window_class";
WNDCLASSEXW extClass{ 0 };
extClass.cbSize = sizeof(WNDCLASSEXW);
extClass.style = CS_HREDRAW | CS_VREDRAW | /*CS_PARENTDC |*/ CS_DBLCLKS;
extClass.lpszClassName = EXTN_WINDOW_CLASS;
extClass.lpfnWndProc = s_ExtensionWindowProc;
extClass.cbWndExtra = GWL_CONSOLE_WNDALLOC; // this is required to store the owning thread/process override in NTUSER
auto extnClassAtom{ RegisterClassExW(&extClass) };
const auto extensionStyle = WS_VISIBLE | WS_CHILD;
const auto extensionStyleEx = 0;
// HAX set up the extension proc BEFORE we set up the window so it can get a WM_CREATE
StartExtension();
g_extension = CreateWindowExW(extensionStyleEx, // WS_EX_LAYERED,
reinterpret_cast<LPCWSTR>(extnClassAtom),
L"Hello Extension",
extensionStyle,
0, // CW_USEDEFAULT,
0, // CW_USEDEFAULT,
50, // CW_USEDEFAULT,
50, // CW_USEDEFAULT,
hwnd, // owner
nullptr,
nullptr,
nullptr);
// pump messages
MSG msg = { 0 };
while (GetMessageW(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
//TODO! much later:
// * The Terminal doesn't think it's active, cause it's HWND... isn't. The extension's is
LRESULT CALLBACK s_ExtensionWindowProc(HWND hWnd,
UINT Message,
WPARAM wParam,
LPARAM lParam)
{
const auto ourResult = _ourExtensionWindowProc(hWnd, Message, wParam, lParam);
if (ourResult != 0)
{
return ourResult;
}
if (g_extensionProc != nullptr)
{
const auto theirResult = g_extensionProc(hWnd, Message, wParam, lParam);
if (theirResult != 0)
{
return theirResult;
}
}
return DefWindowProcW(hWnd, Message, wParam, lParam);
}
LRESULT CALLBACK _ourExtensionWindowProc(HWND hWnd,
UINT Message,
WPARAM wParam,
LPARAM lParam)
{
wParam;
lParam;
switch (Message)
{
case WM_CREATE:
{
// Show the window
ShowWindow(hWnd, SW_SHOWDEFAULT);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 1;
break;
}
}
return 0;
}
void SetExtensionWindowProc(WNDPROC proc)
{
g_extensionProc = proc;
}