895 lines
32 KiB
C++
895 lines
32 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "App.h"
|
|
|
|
#include "TerminalPage.h"
|
|
#include "../WinRTUtils/inc/WtExeUtils.h"
|
|
#include "../../types/inc/utils.hpp"
|
|
#include "Utils.h"
|
|
|
|
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
using namespace winrt::Windows::UI::Text;
|
|
using namespace winrt::Windows::UI::Core;
|
|
using namespace winrt::Windows::Foundation::Collections;
|
|
using namespace winrt::Windows::System;
|
|
using namespace winrt::Microsoft::Terminal;
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
|
using namespace winrt::Microsoft::Terminal::Control;
|
|
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
|
using namespace ::TerminalApp;
|
|
|
|
namespace winrt
|
|
{
|
|
namespace MUX = Microsoft::UI::Xaml;
|
|
using IInspectable = Windows::Foundation::IInspectable;
|
|
}
|
|
|
|
namespace winrt::TerminalApp::implementation
|
|
{
|
|
void TerminalPage::_HandleOpenNewTabDropdown(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_OpenNewTabDropdown();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleDuplicateTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_DuplicateFocusedTab();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto realArgs = args.ActionArgs().try_as<CloseTabArgs>())
|
|
{
|
|
uint32_t index;
|
|
if (realArgs.Index())
|
|
{
|
|
index = realArgs.Index().Value();
|
|
}
|
|
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
index = *focusedTabIndex;
|
|
}
|
|
else
|
|
{
|
|
args.Handled(false);
|
|
return;
|
|
}
|
|
|
|
_CloseTabAtIndex(index);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleClosePane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_CloseFocusedPane();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_CloseRequestedHandlers(nullptr, nullptr);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleQuit(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
RequestQuit();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollUp(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<ScrollUpArgs>();
|
|
if (realArgs)
|
|
{
|
|
_Scroll(ScrollUp, realArgs.RowsToScroll());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollDown(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<ScrollDownArgs>();
|
|
if (realArgs)
|
|
{
|
|
_Scroll(ScrollDown, realArgs.RowsToScroll());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleNextTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<NextTabArgs>();
|
|
if (realArgs)
|
|
{
|
|
_SelectNextTab(true, realArgs.SwitcherMode());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandlePrevTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto& realArgs = args.ActionArgs().try_as<PrevTabArgs>();
|
|
if (realArgs)
|
|
{
|
|
_SelectNextTab(false, realArgs.SwitcherMode());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSendInput(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
args.Handled(false);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<SendInputArgs>())
|
|
{
|
|
if (const auto termControl{ _GetActiveControl() })
|
|
{
|
|
termControl.SendInput(realArgs.Input());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
args.Handled(false);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
|
|
{
|
|
auto moved = _MovePane(realArgs.TabIndex());
|
|
args.Handled(moved);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
args.Handled(false);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<SplitPaneArgs>())
|
|
{
|
|
_SplitPane(realArgs.SplitDirection(),
|
|
// This is safe, we're already filtering so the value is (0, 1)
|
|
::base::saturated_cast<float>(realArgs.SplitSize()),
|
|
_MakePane(realArgs.TerminalArgs(), realArgs.SplitMode() == SplitType::Duplicate));
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleSplitOrientation(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ToggleSplitOrientation();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
// Don't do anything if there's only one pane. It's already zoomed.
|
|
if (activeTab->GetLeafPaneCount() > 1)
|
|
{
|
|
// First thing's first, remove the current content from the UI
|
|
// tree. This is important, because we might be leaving zoom, and if
|
|
// a pane is zoomed, then it's currently in the UI tree, and should
|
|
// be removed before it's re-added in Pane::Restore
|
|
_tabContent.Children().Clear();
|
|
|
|
// Togging the zoom on the tab will cause the tab to inform us of
|
|
// the new root Content for this tab.
|
|
activeTab->ToggleZoom();
|
|
}
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleTogglePaneReadOnly(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
activeTab->TogglePaneReadOnly();
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollPage(ScrollUp);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollDownPage(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollPage(ScrollDown);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollToTop(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollToBufferEdge(ScrollUp);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleScrollToBottom(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_ScrollToBufferEdge(ScrollDown);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleFindMatch(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<FindMatchArgs>())
|
|
{
|
|
if (const auto& control{ _GetActiveControl() })
|
|
{
|
|
control.SearchMatch(realArgs.Direction() == FindMatchDirection::Next);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<OpenSettingsArgs>())
|
|
{
|
|
_LaunchSettings(realArgs.Target());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandlePasteText(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_PasteText();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleNewTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args == nullptr)
|
|
{
|
|
LOG_IF_FAILED(_OpenNewTab(nullptr));
|
|
args.Handled(true);
|
|
}
|
|
else if (const auto& realArgs = args.ActionArgs().try_as<NewTabArgs>())
|
|
{
|
|
LOG_IF_FAILED(_OpenNewTab(realArgs.TerminalArgs()));
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSwitchToTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SwitchToTabArgs>())
|
|
{
|
|
_SelectTab({ realArgs.TabIndex() });
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleResizePane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ResizePaneArgs>())
|
|
{
|
|
if (realArgs.ResizeDirection() == ResizeDirection::None)
|
|
{
|
|
// Do nothing
|
|
args.Handled(false);
|
|
}
|
|
else
|
|
{
|
|
_ResizePane(realArgs.ResizeDirection());
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMoveFocus(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<MoveFocusArgs>())
|
|
{
|
|
if (realArgs.FocusDirection() == FocusDirection::None)
|
|
{
|
|
// Do nothing
|
|
args.Handled(false);
|
|
}
|
|
else
|
|
{
|
|
// Mark as handled only when the move succeeded (e.g. when there
|
|
// is a pane to move to), otherwise mark as unhandled so the
|
|
// keychord can propagate to the terminal (GH#6129)
|
|
const auto moveSucceeded = _MoveFocus(realArgs.FocusDirection());
|
|
args.Handled(moveSucceeded);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSwapPane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SwapPaneArgs>())
|
|
{
|
|
if (realArgs.Direction() == FocusDirection::None)
|
|
{
|
|
// Do nothing
|
|
args.Handled(false);
|
|
}
|
|
else
|
|
{
|
|
auto swapped = _SwapPane(realArgs.Direction());
|
|
args.Handled(swapped);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCopyText(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
|
|
{
|
|
const auto handled = _CopyText(realArgs.SingleLine(), realArgs.CopyFormatting());
|
|
args.Handled(handled);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleAdjustFontSize(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<AdjustFontSizeArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.AdjustFontSize(realArgs.Delta());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleFind(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_Find();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleResetFontSize(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto res = _ApplyToActiveControls([](auto& control) {
|
|
control.ResetFontSize();
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleShaderEffects(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
const auto res = _ApplyToActiveControls([](auto& control) {
|
|
control.ToggleShaderEffects();
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleFocusMode(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
ToggleFocusMode();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleFullscreen(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
ToggleFullscreen();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleAlwaysOnTop(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
ToggleAlwaysOnTop();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleToggleCommandPalette(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ToggleCommandPaletteArgs>())
|
|
{
|
|
CommandPalette().EnableCommandPaletteMode(realArgs.LaunchMode());
|
|
CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ?
|
|
Visibility::Collapsed :
|
|
Visibility::Visible);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSetColorScheme(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
args.Handled(false);
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
|
|
{
|
|
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.ColorScheme(scheme.ToCoreScheme());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleSetTabColor(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
Windows::Foundation::IReference<Windows::UI::Color> tabColor;
|
|
|
|
if (const auto& realArgs = args.ActionArgs().try_as<SetTabColorArgs>())
|
|
{
|
|
tabColor = realArgs.TabColor();
|
|
}
|
|
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
if (tabColor)
|
|
{
|
|
activeTab->SetRuntimeTabColor(tabColor.Value());
|
|
}
|
|
else
|
|
{
|
|
activeTab->ResetRuntimeTabColor();
|
|
}
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
activeTab->ActivateColorPicker();
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleRenameTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
std::optional<winrt::hstring> title;
|
|
|
|
if (const auto& realArgs = args.ActionArgs().try_as<RenameTabArgs>())
|
|
{
|
|
title = realArgs.Title();
|
|
}
|
|
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
if (title.has_value())
|
|
{
|
|
activeTab->SetTabText(title.value());
|
|
}
|
|
else
|
|
{
|
|
activeTab->ResetTabText();
|
|
}
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenTabRenamer(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
activeTab->ActivateTabRenamer();
|
|
}
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleExecuteCommandline(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<ExecuteCommandlineArgs>())
|
|
{
|
|
auto actions = winrt::single_threaded_vector<ActionAndArgs>(std::move(
|
|
TerminalPage::ConvertExecuteCommandlineToActions(realArgs)));
|
|
|
|
if (actions.Size() != 0)
|
|
{
|
|
actionArgs.Handled(true);
|
|
ProcessStartupActions(actions, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseOtherTabs(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<CloseOtherTabsArgs>())
|
|
{
|
|
uint32_t index;
|
|
if (realArgs.Index())
|
|
{
|
|
index = realArgs.Index().Value();
|
|
}
|
|
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
index = *focusedTabIndex;
|
|
}
|
|
else
|
|
{
|
|
// Do nothing
|
|
actionArgs.Handled(false);
|
|
return;
|
|
}
|
|
|
|
// Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove
|
|
std::vector<winrt::TerminalApp::TabBase> tabsToRemove;
|
|
if (index > 0)
|
|
{
|
|
std::copy(begin(_tabs), begin(_tabs) + index, std::back_inserter(tabsToRemove));
|
|
}
|
|
|
|
if (index + 1 < _tabs.Size())
|
|
{
|
|
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
|
}
|
|
|
|
_RemoveTabs(tabsToRemove);
|
|
|
|
actionArgs.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleCloseTabsAfter(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<CloseTabsAfterArgs>())
|
|
{
|
|
uint32_t index;
|
|
if (realArgs.Index())
|
|
{
|
|
index = realArgs.Index().Value();
|
|
}
|
|
else if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
index = *focusedTabIndex;
|
|
}
|
|
else
|
|
{
|
|
// Do nothing
|
|
actionArgs.Handled(false);
|
|
return;
|
|
}
|
|
|
|
// Since _RemoveTabs is asynchronous, create a snapshot of the tabs we want to remove
|
|
std::vector<winrt::TerminalApp::TabBase> tabsToRemove;
|
|
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
|
_RemoveTabs(tabsToRemove);
|
|
|
|
// TODO:GH#7182 For whatever reason, if you run this action
|
|
// when the tab that's currently focused is _before_ the `index`
|
|
// param, then the tabs will expand to fill the entire width of the
|
|
// tab row, until you mouse over them. Probably has something to do
|
|
// with tabs not resizing down until there's a mouse exit event.
|
|
|
|
actionArgs.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleTabSearch(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
CommandPalette().SetTabs(_tabs, _mruTabs);
|
|
CommandPalette().EnableTabSearchMode();
|
|
CommandPalette().Visibility(Visibility::Visible);
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleMoveTab(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<MoveTabArgs>())
|
|
{
|
|
auto direction = realArgs.Direction();
|
|
if (direction != MoveTabDirection::None)
|
|
{
|
|
if (auto focusedTabIndex = _GetFocusedTabIndex())
|
|
{
|
|
auto currentTabIndex = focusedTabIndex.value();
|
|
auto delta = direction == MoveTabDirection::Forward ? 1 : -1;
|
|
_TryMoveTab(currentTabIndex, currentTabIndex + delta);
|
|
}
|
|
}
|
|
actionArgs.Handled(true);
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleBreakIntoDebugger(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
if (_settings.GlobalSettings().DebugFeaturesEnabled())
|
|
{
|
|
actionArgs.Handled(true);
|
|
DebugBreak();
|
|
}
|
|
}
|
|
|
|
// Function Description:
|
|
// - Helper to launch a new WT instance. It can either launch the instance
|
|
// elevated or unelevated.
|
|
// - To launch elevated, it will as the shell to elevate the process for us.
|
|
// This might cause a UAC prompt. The elevation is performed on a
|
|
// background thread, as to not block the UI thread.
|
|
// Arguments:
|
|
// - elevate: If true, launch the new Terminal elevated using `runas`
|
|
// - newTerminalArgs: A NewTerminalArgs describing the terminal instance
|
|
// that should be spawned. The Profile should be filled in with the GUID
|
|
// of the profile we want to launch.
|
|
// Return Value:
|
|
// - <none>
|
|
// Important: Don't take the param by reference, since we'll be doing work
|
|
// on another thread.
|
|
fire_and_forget TerminalPage::_OpenNewWindow(const bool elevate,
|
|
const NewTerminalArgs newTerminalArgs)
|
|
{
|
|
// Hop to the BG thread
|
|
co_await winrt::resume_background();
|
|
|
|
// This will get us the correct exe for dev/preview/release. If you
|
|
// don't stick this in a local, it'll get mangled by ShellExecute. I
|
|
// have no idea why.
|
|
const auto exePath{ GetWtExePath() };
|
|
|
|
// Build the commandline to pass to wt for this set of NewTerminalArgs
|
|
// `-w -1` will ensure a new window is created.
|
|
winrt::hstring cmdline{
|
|
fmt::format(L"-w -1 new-tab {}",
|
|
newTerminalArgs ? newTerminalArgs.ToCommandline().c_str() :
|
|
L"")
|
|
};
|
|
|
|
// Build the args to ShellExecuteEx. We need to use ShellExecuteEx so we
|
|
// can pass the SEE_MASK_NOASYNC flag. That flag allows us to safely
|
|
// call this on the background thread, and have ShellExecute _not_ call
|
|
// back to us on the main thread. Without this, if you close the
|
|
// Terminal quickly after the UAC prompt, the elevated WT will never
|
|
// actually spawn.
|
|
SHELLEXECUTEINFOW seInfo{ 0 };
|
|
seInfo.cbSize = sizeof(seInfo);
|
|
seInfo.fMask = SEE_MASK_NOASYNC;
|
|
// `runas` will cause the shell to launch this child process elevated.
|
|
// `open` will just run the executable normally.
|
|
seInfo.lpVerb = elevate ? L"runas" : L"open";
|
|
seInfo.lpFile = exePath.c_str();
|
|
seInfo.lpParameters = cmdline.c_str();
|
|
seInfo.nShow = SW_SHOWNORMAL;
|
|
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
|
|
|
|
co_return;
|
|
}
|
|
|
|
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& actionArgs)
|
|
{
|
|
NewTerminalArgs newTerminalArgs{ nullptr };
|
|
// If the caller provided NewTerminalArgs, then try to use those
|
|
if (actionArgs)
|
|
{
|
|
if (const auto& realArgs = actionArgs.ActionArgs().try_as<NewWindowArgs>())
|
|
{
|
|
newTerminalArgs = realArgs.TerminalArgs();
|
|
}
|
|
}
|
|
// Otherwise, if no NewTerminalArgs were provided, then just use a
|
|
// default-constructed one. The default-constructed one implies that
|
|
// nothing about the launch should be modified (just use the default
|
|
// profile).
|
|
if (!newTerminalArgs)
|
|
{
|
|
newTerminalArgs = NewTerminalArgs();
|
|
}
|
|
|
|
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
|
|
|
|
// Manually fill in the evaluated profile.
|
|
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
|
|
_OpenNewWindow(false, newTerminalArgs);
|
|
actionArgs.Handled(true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Raise a IdentifyWindowsRequested event. This will bubble up to the
|
|
// AppLogic, to the AppHost, to the Peasant, to the Monarch, then get
|
|
// distributed down to _all_ the Peasants, as to display info about the
|
|
// window in _every_ Peasant window.
|
|
// - This action is also buggy right now, because TeachingTips behave
|
|
// weird in XAML Islands. See microsoft-ui-xaml#4382
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void TerminalPage::_HandleIdentifyWindows(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_IdentifyWindowsRequestedHandlers(*this, nullptr);
|
|
args.Handled(true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Display the "Toast" with the name and ID of this window.
|
|
// - Unlike _HandleIdentifyWindow**s**, this event just displays the window
|
|
// ID and name in the current window. It does not involve any bubbling
|
|
// up/down the page/logic/host/manager/peasant/monarch.
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void TerminalPage::_HandleIdentifyWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
IdentifyWindow();
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleRenameWindow(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<RenameWindowArgs>())
|
|
{
|
|
const auto newName = realArgs.Name();
|
|
const auto request = winrt::make_self<implementation::RenameWindowRequestedArgs>(newName);
|
|
_RenameWindowRequestedHandlers(*this, *request);
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenWindowRenamer(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (WindowRenamer() == nullptr)
|
|
{
|
|
// We need to use FindName to lazy-load this object
|
|
if (MUX::Controls::TeachingTip tip{ FindName(L"WindowRenamer").try_as<MUX::Controls::TeachingTip>() })
|
|
{
|
|
tip.Closed({ get_weak(), &TerminalPage::_FocusActiveControl });
|
|
}
|
|
}
|
|
|
|
_UpdateTeachingTipTheme(WindowRenamer().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
|
|
WindowRenamer().IsOpen(true);
|
|
|
|
// PAIN: We can't immediately focus the textbox in the TeachingTip. It's
|
|
// not technically focusable until it is opened. However, it doesn't
|
|
// provide an event to tell us when it is opened. That's tracked in
|
|
// microsoft/microsoft-ui-xaml#1607. So for now, the user _needs_ to
|
|
// click on the text box manually.
|
|
//
|
|
// We're also not using a ContentDialog for this, because in Xaml
|
|
// Islands a text box in a ContentDialog won't receive _any_ keypresses.
|
|
// Fun!
|
|
// WindowRenamerTextBox().Focus(FocusState::Programmatic);
|
|
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleGlobalSummon(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
// Manually return false. These shouldn't ever get here, except for when
|
|
// we fail to register for the global hotkey. In that case, returning
|
|
// false here will let the underlying terminal still process the key, as
|
|
// if it wasn't bound at all.
|
|
args.Handled(false);
|
|
}
|
|
void TerminalPage::_HandleQuakeMode(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
// Manually return false. These shouldn't ever get here, except for when
|
|
// we fail to register for the global hotkey. In that case, returning
|
|
// false here will let the underlying terminal still process the key, as
|
|
// if it wasn't bound at all.
|
|
args.Handled(false);
|
|
}
|
|
|
|
void TerminalPage::_HandleFocusPane(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<FocusPaneArgs>())
|
|
{
|
|
const auto paneId = realArgs.Id();
|
|
if (const auto activeTab{ _GetFocusedTabImpl() })
|
|
{
|
|
_UnZoomIfNeeded();
|
|
args.Handled(activeTab->FocusPane(paneId));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleOpenSystemMenu(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
_OpenSystemMenuHandlers(*this, nullptr);
|
|
args.Handled(true);
|
|
}
|
|
|
|
void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<ClearBufferArgs>())
|
|
{
|
|
const auto res = _ApplyToActiveControls([&](auto& control) {
|
|
control.ClearBuffer(realArgs.Clear());
|
|
});
|
|
args.Handled(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalPage::_HandleMultipleActions(const IInspectable& /*sender*/,
|
|
const ActionEventArgs& args)
|
|
{
|
|
if (args)
|
|
{
|
|
if (const auto& realArgs = args.ActionArgs().try_as<MultipleActionsArgs>())
|
|
{
|
|
for (const auto& action : realArgs.Actions())
|
|
{
|
|
_actionDispatch->DoAction(action);
|
|
}
|
|
|
|
args.Handled(true);
|
|
}
|
|
}
|
|
}
|
|
}
|