I wanted to do this in one shot but _zelda_

This commit is contained in:
Mike Griese
2023-05-12 13:32:12 -05:00
parent c553b2123d
commit f353323a23
9 changed files with 906 additions and 536 deletions

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
interface IPaneContent
{
Windows.UI.Xaml.FrameworkElement GetRoot();
Windows.Foundation.Size MinSize { get; };
String Title { get; };
UInt64 TaskbarState { get; };
UInt64 TaskbarProgress { get; };
Boolean ReadOnly { get; };
void Focus();
void Close();
// event CloseRequested(...);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
#pragma once
#include "TaskbarState.h"
#include "TerminalPaneContent.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -60,8 +61,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,
@@ -80,7 +80,11 @@ 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();
@@ -232,10 +236,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;
@@ -245,16 +247,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;
} _controlEvents;
void _setupControlEvents();
void _removeControlEvents();
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
@@ -264,13 +256,14 @@ private:
bool _zoomed{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
winrt::Windows::Media::Playback::MediaPlayer::MediaEnded_revoker _mediaEndedRevoker;
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,
@@ -300,15 +293,15 @@ private:
void _Focus();
void _FocusFirstChild();
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 _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,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& 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*/);
// 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;
@@ -320,7 +313,7 @@ private:
SplitState _convertAutomaticOrDirectionalSplitState(const winrt::Microsoft::Terminal::Settings::Model::SplitDirection& splitType) const;
winrt::fire_and_forget _playBellSound(winrt::Windows::Foundation::Uri uri);
// 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

View File

@@ -153,6 +153,9 @@
<ClInclude Include="SettingsLoadEventArgs.h">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalPaneContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Toast.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
@@ -255,6 +258,9 @@
<ClCompile Include="TerminalWindow.cpp">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalPaneContent.cpp">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="Toast.cpp" />
</ItemGroup>
@@ -322,6 +328,8 @@
</Midl>
<Midl Include="FilteredCommand.idl" />
<Midl Include="EmptyStringVisibilityConverter.idl" />
<Midl Include="IPaneContent.idl" />
<Midl Include="TerminalPaneContent.idl" />
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>

View File

@@ -2960,13 +2960,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

View File

@@ -0,0 +1,235 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TerminalPaneContent.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, { this, &TerminalPaneContent::_ControlWarningBellHandler });
_controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { this, &TerminalPaneContent::_CloseTerminalRequestedHandler });
_controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { this, &TerminalPaneContent::_RestartTerminalRequestedHandler });
}
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()
{
_control.Focus(FocusState::Programmatic);
}
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.
_mediaEndedRevoker.revoke();
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
}
_bellPlayer = 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))
{
// TODO! ask the Pane to close
// 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();
}
// TODO!
// // 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));
}
}
}
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() })
{
// BODGY
// GH#12258: We learned that if you leave the MediaPlayer open, and
// press the media keys (like play/pause), then the OS will _replay the
// bell_. So we have to re-create the MediaPlayer each time we want to
// play the bell, to make sure a subsequent play doesn't come through
// and reactivate the old one.
if (!_bellPlayer)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
}
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();
// This lambda will clean up the bell player when we're done with it.
auto weakThis2{ get_weak() };
_mediaEndedRevoker = _bellPlayer.MediaEnded(winrt::auto_revoke, [weakThis2](auto&&, auto&&) {
if (auto self{ weakThis2.get() })
{
if (self->_bellPlayer)
{
// We need to make sure clear out the current track
// that's being played, again, so that the system can't
// come through and replay it. In testing, we needed to
// do this, closing the MediaPlayer alone wasn't good
// enough.
self->_bellPlayer.Pause();
self->_bellPlayer.Source(nullptr);
self->_bellPlayer.Close();
}
self->_mediaEndedRevoker.revoke();
self->_bellPlayer = nullptr;
}
});
}
}
}
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*/)
{
_RestartTerminalRequestedHandlers(*this, nullptr);
}
void TerminalPaneContent::UpdateSettings(const TerminalSettingsCreateResult& settings,
const Profile& profile)
{
_profile = profile;
_control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings());
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "TerminalPaneContent.g.h"
#include "../../cascadia/inc/cppwinrt_utils.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();
void Close();
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
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(); }
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 };
winrt::Windows::Media::Playback::MediaPlayer::MediaEnded_revoker _mediaEndedRevoker;
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;
} _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 _ControlGotFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
// const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
// void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
// const winrt::Windows::UI::Xaml::RoutedEventArgs& 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*/);
};
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "IPaneContent.idl";
namespace TerminalApp
{
[default_interface] runtimeclass TerminalPaneContent : IPaneContent
{
Microsoft.Terminal.Control.TermControl GetTerminal();
void UpdateSettings(const Microsoft.Terminal.Settings.Model.TerminalSettingsCreateResult settings,
const Microsoft.Terminal.Settings.Model.Profile profile);
Microsoft.Terminal.Settings.Model.Profile GetProfile();
}
}

View File

@@ -507,7 +507,11 @@ namespace winrt::TerminalApp::implementation
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
_AttachEventHandlersToControl(p->Id().value(), p->_control);
// TODO! this feels hacky
if (const auto& termPane{ p->_content.try_as<TerminalApp::TerminalPaneContent>() })
{
_AttachEventHandlersToControl(p->Id().value(), termPane.GetTerminal());
}
_nextPaneId++;
}
return false;
@@ -616,7 +620,12 @@ namespace winrt::TerminalApp::implementation
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
_AttachEventHandlersToControl(p->Id().value(), p->_control);
// TODO! this feels hacky
if (const auto& termPane{ p->_content.try_as<TerminalApp::TerminalPaneContent>() })
{
_AttachEventHandlersToControl(p->Id().value(), termPane.GetTerminal());
}
_nextPaneId++;
}
});