Add support for the newWindow
action (#9208)
Finally implements the `newWindow` action. It does so by `ShellExecute`ing `wt.exe` with commandline args corresponding to the ones that would create the same `NewTerminalArgs`. This works with #8898 and #9118 to allow new windows (even with `windowingBehavior: useExisting`) This is taken from my auto-elevate branch, hence the references to elevation References #5000 References projects/5 References #8898 References #9118 Closes #1051
This commit is contained in:
parent
eb0fb3e822
commit
049e37e514
2
.github/actions/spelling/dictionary/apis.txt
vendored
2
.github/actions/spelling/dictionary/apis.txt
vendored
|
@ -58,6 +58,7 @@ NCHITTEST
|
|||
NCLBUTTONDBLCLK
|
||||
NCRBUTTONDBLCLK
|
||||
NOAGGREGATION
|
||||
NOASYNC
|
||||
NOPROGRESS
|
||||
NOREDIRECTIONBITMAP
|
||||
ntprivapi
|
||||
|
@ -82,6 +83,7 @@ shobjidl
|
|||
SIZENS
|
||||
smoothstep
|
||||
GETDESKWALLPAPER
|
||||
SHELLEXECUTEINFOW
|
||||
snprintf
|
||||
spsc
|
||||
sregex
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
"moveFocus",
|
||||
"moveTab",
|
||||
"newTab",
|
||||
"newWindow",
|
||||
"nextTab",
|
||||
"openNewTabDropdown",
|
||||
"openSettings",
|
||||
|
@ -584,6 +585,18 @@
|
|||
],
|
||||
"required": [ "direction" ]
|
||||
},
|
||||
"NewWindowAction": {
|
||||
"description": "Arguments corresponding to a New Window Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{ "$ref": "#/definitions/NewTerminalArgs" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type":"string", "pattern": "newWindow" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
|
@ -609,6 +622,7 @@
|
|||
{ "$ref": "#/definitions/ScrollDownAction" },
|
||||
{ "$ref": "#/definitions/MoveTabAction" },
|
||||
{ "$ref": "#/definitions/FindMatchAction" },
|
||||
{ "$ref": "#/definitions/NewWindowAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
|
|
|
@ -42,6 +42,8 @@ namespace SettingsModelLocalTests
|
|||
TEST_METHOD(TestAutogeneratedName);
|
||||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
|
||||
TEST_METHOD(TestGenerateCommandline);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
|
@ -361,4 +363,145 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestGenerateCommandline()
|
||||
{
|
||||
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{
|
||||
"name":"action0",
|
||||
"command": { "action": "newWindow" }
|
||||
},
|
||||
{
|
||||
"name":"action1",
|
||||
"command": { "action": "newTab", "profile": "foo" }
|
||||
},
|
||||
{
|
||||
"name":"action2",
|
||||
"command": { "action": "newWindow", "profile": "foo" }
|
||||
},
|
||||
{
|
||||
"name":"action3",
|
||||
"command": { "action": "newWindow", "commandline": "bar.exe" }
|
||||
},
|
||||
{
|
||||
"name":"action4",
|
||||
"command": { "action": "newWindow", "commandline": "pop.exe ya ha ha" }
|
||||
},
|
||||
{
|
||||
"name":"action5",
|
||||
"command": { "action": "newWindow", "commandline": "pop.exe \"ya ha ha\"" }
|
||||
},
|
||||
{
|
||||
"name":"action6",
|
||||
"command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" }
|
||||
},
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(7u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action3");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
VERIFY_ARE_EQUAL(L"-- \"bar.exe\"", cmdline);
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"-- \"pop.exe ya ha ha\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"-- \"pop.exe \"ya ha ha\"\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"action6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewWindowArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
const auto& terminalArgs = realArgs.TerminalArgs();
|
||||
VERIFY_IS_NOT_NULL(terminalArgs);
|
||||
auto cmdline = terminalArgs.ToCommandline();
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cmdline: \"%s\"", cmdline.c_str()));
|
||||
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "OpenTerminalHere.h"
|
||||
#include "../WinRTUtils/inc/WtExeUtils.h"
|
||||
#include <ShlObj.h>
|
||||
|
||||
// TODO GH#6112: Localize these strings
|
||||
|
@ -10,103 +11,10 @@ static constexpr std::wstring_view VerbDisplayName{ L"Open in Windows Terminal"
|
|||
static constexpr std::wstring_view VerbDevBuildDisplayName{ L"Open in Windows Terminal (Dev Build)" };
|
||||
static constexpr std::wstring_view VerbName{ L"WindowsTerminalOpenHere" };
|
||||
|
||||
static constexpr std::wstring_view WtExe{ L"wt.exe" };
|
||||
static constexpr std::wstring_view WtdExe{ L"wtd.exe" };
|
||||
static constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" };
|
||||
|
||||
static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" };
|
||||
|
||||
// This code is aggressively copied from
|
||||
// https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/
|
||||
// Win7Samples/winui/shell/appshellintegration/ExplorerCommandVerb/ExplorerCommandVerb.cpp
|
||||
|
||||
// Function Description:
|
||||
// - This is a helper to determine if we're running as a part of the Dev Build
|
||||
// Package or the release package. We'll need to return different text, icons,
|
||||
// and use different commandlines depending on which one the user requested.
|
||||
// - Uses a C++11 "magic static" to make sure this is only computed once.
|
||||
// - If we can't determine if it's the dev build or not, we'll default to true
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if we believe this extension is being run in the dev build package.
|
||||
static bool IsDevBuild()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static bool isDevBuild = []() -> bool {
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto id = package.Id();
|
||||
const std::wstring name{ id.FullName() };
|
||||
// Does our PFN start with WindowsTerminalDev?
|
||||
return name.rfind(L"WindowsTerminalDev", 0) == 0;
|
||||
}
|
||||
CATCH_LOG();
|
||||
return true;
|
||||
}();
|
||||
|
||||
return isDevBuild;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function for getting the path to the appropriate executable to use
|
||||
// for this instance of the shell extension. If we're running the dev build,
|
||||
// it should be a `wtd.exe`, but if we're preview or release, we want to make
|
||||
// sure to get the correct `wt.exe` that corresponds to _us_.
|
||||
// - If we're unpackaged, this needs to get us `WindowsTerminal.exe`, because
|
||||
// the `wt*exe` alias won't have been installed for this install.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the full path to the exe, one of `wt.exe`, `wtd.exe`, or `WindowsTerminal.exe`.
|
||||
static std::wstring _getExePath()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static const std::wstring exePath = []() -> std::wstring {
|
||||
// First, check a packaged location for the exe. If we've got a package
|
||||
// family name, that means we're one of the packaged Dev build, packaged
|
||||
// Release build, or packaged Preview build.
|
||||
//
|
||||
// If we're the preview or release build, there's no way of knowing if the
|
||||
// `wt.exe` on the %PATH% is us or not. Fortunately, _our_ execution alias
|
||||
// is located in "%LOCALAPPDATA%\Microsoft\WindowsApps\<our package family
|
||||
// name>", _always_, so we can use that to look up the exe easier.
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto id = package.Id();
|
||||
const std::wstring pfn{ id.FamilyName() };
|
||||
if (!pfn.empty())
|
||||
{
|
||||
const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW<std::wstring>(LocalAppDataAppsPath.data()) };
|
||||
const std::filesystem::path wtPath = windowsAppsPath / pfn / (IsDevBuild() ? WtdExe : WtExe);
|
||||
return wtPath;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
// If we're here, then we couldn't resolve our exe from the package. This
|
||||
// means we're running unpackaged. We should just use the
|
||||
// WindowsTerminal.exe that's sitting in the directory next to us.
|
||||
try
|
||||
{
|
||||
HMODULE hModule = GetModuleHandle(nullptr);
|
||||
THROW_LAST_ERROR_IF(hModule == nullptr);
|
||||
std::wstring dllPathString;
|
||||
THROW_IF_FAILED(wil::GetModuleFileNameW(hModule, dllPathString));
|
||||
const std::filesystem::path dllPath{ dllPathString };
|
||||
const std::filesystem::path rootDir = dllPath.parent_path();
|
||||
std::filesystem::path wtPath = rootDir / WindowsTerminalExe;
|
||||
return wtPath;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
return L"wt.exe";
|
||||
}();
|
||||
return exePath;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method is called when the user activates the context menu item. We'll
|
||||
// launch the Terminal using the current working directory.
|
||||
|
@ -148,7 +56,7 @@ HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
|
|||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
|
||||
|
||||
// Append a "\." to the given path, so that this will work in "C:\"
|
||||
std::wstring cmdline = fmt::format(L"\"{}\" -d \"{}\\.\"", _getExePath(), pszName.get());
|
||||
std::wstring cmdline = fmt::format(L"\"{}\" -d \"{}\\.\"", GetWtExePath(), pszName.get());
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
|
||||
nullptr,
|
||||
cmdline.data(),
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#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;
|
||||
|
@ -581,4 +583,89 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// 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 _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();
|
||||
}
|
||||
|
||||
auto [profileGuid, settings] = TerminalSettings::BuildSettings(_settings,
|
||||
newTerminalArgs,
|
||||
*_bindings);
|
||||
|
||||
// Manually fill in the evaluated profile.
|
||||
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profileGuid));
|
||||
_OpenNewWindow(false, newTerminalArgs);
|
||||
actionArgs.Handled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,12 +60,6 @@ namespace winrt::TerminalApp::implementation
|
|||
_NewTabHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
|
||||
case ShortcutAction::NewWindow:
|
||||
{
|
||||
_NewWindowHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::CloseWindow:
|
||||
{
|
||||
_CloseWindowHandlers(*this, eventArgs);
|
||||
|
@ -266,6 +260,11 @@ namespace winrt::TerminalApp::implementation
|
|||
_TogglePaneReadOnlyHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::NewWindow:
|
||||
{
|
||||
_NewWindowHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ namespace winrt::TerminalApp::implementation
|
|||
TYPED_EVENT(OpenNewTabDropdown, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
|
@ -67,6 +66,7 @@ namespace winrt::TerminalApp::implementation
|
|||
TYPED_EVENT(BreakIntoDebugger, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(FindMatch, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(TogglePaneReadOnly, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace TerminalApp
|
|||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> NewTab;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> OpenNewTabDropdown;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> DuplicateTab;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> NewWindow;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> CloseWindow;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> CloseTab;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> ClosePane;
|
||||
|
@ -53,5 +52,6 @@ namespace TerminalApp
|
|||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> BreakIntoDebugger;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> FindMatch;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> TogglePaneReadOnly;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> NewWindow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1133,6 +1133,7 @@ namespace winrt::TerminalApp::implementation
|
|||
_actionDispatch->BreakIntoDebugger({ this, &TerminalPage::_HandleBreakIntoDebugger });
|
||||
_actionDispatch->FindMatch({ this, &TerminalPage::_HandleFindMatch });
|
||||
_actionDispatch->TogglePaneReadOnly({ this, &TerminalPage::_HandleTogglePaneReadOnly });
|
||||
_actionDispatch->NewWindow({ this, &TerminalPage::_HandleNewWindow });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -319,6 +319,7 @@ namespace winrt::TerminalApp::implementation
|
|||
void _HandleBreakIntoDebugger(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
void _HandleFindMatch(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
void _HandleTogglePaneReadOnly(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
void _HandleNewWindow(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
|
||||
// Make sure to hook new actions up in _RegisterActionCallbacks!
|
||||
#pragma endregion
|
||||
|
|
|
@ -19,7 +19,6 @@ static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
|
|||
static constexpr std::string_view FindKey{ "find" };
|
||||
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
|
||||
static constexpr std::string_view NewTabKey{ "newTab" };
|
||||
static constexpr std::string_view NewWindowKey{ "newWindow" };
|
||||
static constexpr std::string_view NextTabKey{ "nextTab" };
|
||||
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
|
||||
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
|
||||
|
@ -53,6 +52,7 @@ static constexpr std::string_view MoveTabKey{ "moveTab" };
|
|||
static constexpr std::string_view BreakIntoDebuggerKey{ "breakIntoDebugger" };
|
||||
static constexpr std::string_view FindMatchKey{ "findMatch" };
|
||||
static constexpr std::string_view TogglePaneReadOnlyKey{ "toggleReadOnlyMode" };
|
||||
static constexpr std::string_view NewWindowKey{ "newWindow" };
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
|
@ -84,7 +84,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{ FindKey, ShortcutAction::Find },
|
||||
{ MoveFocusKey, ShortcutAction::MoveFocus },
|
||||
{ NewTabKey, ShortcutAction::NewTab },
|
||||
{ NewWindowKey, ShortcutAction::NewWindow },
|
||||
{ NextTabKey, ShortcutAction::NextTab },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
{ OpenSettingsKey, ShortcutAction::OpenSettings },
|
||||
|
@ -119,6 +118,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{ UnboundKey, ShortcutAction::Invalid },
|
||||
{ FindMatchKey, ShortcutAction::FindMatch },
|
||||
{ TogglePaneReadOnlyKey, ShortcutAction::TogglePaneReadOnly },
|
||||
{ NewWindowKey, ShortcutAction::NewWindow },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<SettingsLoadWarnings>>;
|
||||
|
@ -150,6 +150,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{ ShortcutAction::MoveTab, MoveTabArgs::FromJson },
|
||||
{ ShortcutAction::ToggleCommandPalette, ToggleCommandPaletteArgs::FromJson },
|
||||
{ ShortcutAction::FindMatch, FindMatchArgs::FromJson },
|
||||
{ ShortcutAction::NewWindow, NewWindowArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::Invalid, nullptr },
|
||||
};
|
||||
|
@ -288,7 +289,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{ ShortcutAction::Invalid, L"" },
|
||||
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
|
||||
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
|
||||
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
|
||||
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
|
||||
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
|
||||
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
|
||||
|
@ -321,6 +321,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{ ShortcutAction::BreakIntoDebugger, RS_(L"BreakIntoDebuggerCommandKey") },
|
||||
{ ShortcutAction::FindMatch, L"" }, // Intentionally omitted, must be generated by GenerateName
|
||||
{ ShortcutAction::TogglePaneReadOnly, RS_(L"TogglePaneReadOnlyCommandKey") },
|
||||
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
|
||||
};
|
||||
}();
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "MoveTabArgs.g.cpp"
|
||||
#include "FindMatchArgs.g.cpp"
|
||||
#include "ToggleCommandPaletteArgs.g.cpp"
|
||||
#include "NewWindowArgs.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
|
@ -76,6 +77,53 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring NewTerminalArgs::ToCommandline() const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
||||
if (!_Profile.empty())
|
||||
{
|
||||
ss << fmt::format(L"--profile \"{}\" ", _Profile);
|
||||
}
|
||||
// The caller is always expected to provide the evaluated profile in the
|
||||
// NewTerminalArgs, not the index
|
||||
//
|
||||
// else if (_ProfileIndex)
|
||||
// {
|
||||
// ss << fmt::format(L"profile index: {}, ", _ProfileIndex.Value());
|
||||
// }
|
||||
|
||||
if (!_StartingDirectory.empty())
|
||||
{
|
||||
ss << fmt::format(L"--startingDirectory \"{}\" ", _StartingDirectory);
|
||||
}
|
||||
|
||||
if (!_TabTitle.empty())
|
||||
{
|
||||
ss << fmt::format(L"--title \"{}\" ", _TabTitle);
|
||||
}
|
||||
|
||||
if (_TabColor)
|
||||
{
|
||||
const til::color tabColor{ _TabColor.Value() };
|
||||
ss << fmt::format(L"--tabColor \"{}\" ", tabColor.ToHexString(true));
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
{
|
||||
ss << fmt::format(L"-- \"{}\" ", _Commandline);
|
||||
}
|
||||
|
||||
auto s = ss.str();
|
||||
if (s.empty())
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Chop off the last " "
|
||||
return winrt::hstring{ s.substr(0, s.size() - 1) };
|
||||
}
|
||||
|
||||
winrt::hstring CopyTextArgs::GenerateName() const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
@ -444,4 +492,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
}
|
||||
return L"";
|
||||
}
|
||||
|
||||
winrt::hstring NewWindowArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (newTerminalArgsStr.empty())
|
||||
{
|
||||
return RS_(L"NewWindowCommandKey");
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, {}", RS_(L"NewWindowCommandKey"), newTerminalArgsStr)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "MoveTabArgs.g.h"
|
||||
#include "ToggleCommandPaletteArgs.g.h"
|
||||
#include "FindMatchArgs.g.h"
|
||||
#include "NewWindowArgs.g.h"
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "JsonUtils.h"
|
||||
|
@ -76,6 +77,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
hstring ToCommandline() const;
|
||||
|
||||
bool Equals(const Model::NewTerminalArgs& other)
|
||||
{
|
||||
|
@ -883,6 +885,40 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
}
|
||||
};
|
||||
|
||||
struct NewWindowArgs : public NewWindowArgsT<NewWindowArgs>
|
||||
{
|
||||
NewWindowArgs() = default;
|
||||
NewWindowArgs(const Model::NewTerminalArgs& terminalArgs) :
|
||||
_TerminalArgs{ terminalArgs } {};
|
||||
GETSET_PROPERTY(Model::NewTerminalArgs, TerminalArgs, nullptr);
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<NewWindowArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_TerminalArgs.Equals(_TerminalArgs);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<NewWindowArgs>();
|
||||
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
|
||||
return { *args, {} };
|
||||
}
|
||||
IActionArgs Copy() const
|
||||
{
|
||||
auto copy{ winrt::make_self<NewWindowArgs>() };
|
||||
copy->_TerminalArgs = _TerminalArgs.Copy();
|
||||
return *copy;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
|
@ -899,4 +935,5 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
|||
BASIC_FACTORY(MoveTabArgs);
|
||||
BASIC_FACTORY(OpenSettingsArgs);
|
||||
BASIC_FACTORY(FindMatchArgs);
|
||||
BASIC_FACTORY(NewWindowArgs);
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
|
||||
Boolean Equals(NewTerminalArgs other);
|
||||
String GenerateName();
|
||||
String ToCommandline();
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
|
||||
|
@ -217,4 +218,10 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
FindMatchArgs(FindMatchDirection direction);
|
||||
FindMatchDirection Direction { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewWindowArgs : IActionArgs
|
||||
{
|
||||
NewWindowArgs(NewTerminalArgs terminalArgs);
|
||||
NewTerminalArgs TerminalArgs { get; };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
OpenNewTabDropdown,
|
||||
DuplicateTab,
|
||||
NewTab,
|
||||
NewWindow,
|
||||
CloseWindow,
|
||||
CloseTab,
|
||||
ClosePane,
|
||||
|
@ -53,8 +52,9 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
TabSearch,
|
||||
MoveTab,
|
||||
BreakIntoDebugger,
|
||||
TogglePaneReadOnly,
|
||||
FindMatch,
|
||||
TogglePaneReadOnly
|
||||
NewWindow
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionAndArgs {
|
||||
|
|
|
@ -303,6 +303,7 @@
|
|||
{ "command": { "action" : "moveTab", "direction": "forward" }},
|
||||
{ "command": { "action" : "moveTab", "direction": "backward" }},
|
||||
{ "command": "newTab", "keys": "ctrl+shift+t" },
|
||||
{ "command": "newWindow", "keys": "ctrl+shift+n" },
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
|
||||
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
|
||||
{ "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3" },
|
||||
|
|
93
src/cascadia/WinRTUtils/inc/WtExeUtils.h
Normal file
93
src/cascadia/WinRTUtils/inc/WtExeUtils.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
|
||||
static constexpr std::wstring_view WtExe{ L"wt.exe" };
|
||||
static constexpr std::wstring_view WtdExe{ L"wtd.exe" };
|
||||
static constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" };
|
||||
|
||||
static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" };
|
||||
|
||||
// Function Description:
|
||||
// - This is a helper to determine if we're running as a part of the Dev Build
|
||||
// Package or the release package. We'll need to return different text, icons,
|
||||
// and use different commandlines depending on which one the user requested.
|
||||
// - Uses a C++11 "magic static" to make sure this is only computed once.
|
||||
// - If we can't determine if it's the dev build or not, we'll default to true
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if we believe this extension is being run in the dev build package.
|
||||
_TIL_INLINEPREFIX bool IsDevBuild()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static bool isDevBuild = []() -> bool {
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto id = package.Id();
|
||||
const std::wstring name{ id.FullName() };
|
||||
// Does our PFN start with WindowsTerminalDev?
|
||||
return name.rfind(L"WindowsTerminalDev", 0) == 0;
|
||||
}
|
||||
CATCH_LOG();
|
||||
return true;
|
||||
}();
|
||||
|
||||
return isDevBuild;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function for getting the path to the appropriate executable to use
|
||||
// for this instance of the shell extension. If we're running the dev build,
|
||||
// it should be a `wtd.exe`, but if we're preview or release, we want to make
|
||||
// sure to get the correct `wt.exe` that corresponds to _us_.
|
||||
// - If we're unpackaged, this needs to get us `WindowsTerminal.exe`, because
|
||||
// the `wt*exe` alias won't have been installed for this install.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the full path to the exe, one of `wt.exe`, `wtd.exe`, or `WindowsTerminal.exe`.
|
||||
_TIL_INLINEPREFIX std::wstring GetWtExePath()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static const std::wstring exePath = []() -> std::wstring {
|
||||
// First, check a packaged location for the exe. If we've got a package
|
||||
// family name, that means we're one of the packaged Dev build, packaged
|
||||
// Release build, or packaged Preview build.
|
||||
//
|
||||
// If we're the preview or release build, there's no way of knowing if the
|
||||
// `wt.exe` on the %PATH% is us or not. Fortunately, _our_ execution alias
|
||||
// is located in "%LOCALAPPDATA%\Microsoft\WindowsApps\<our package family
|
||||
// name>", _always_, so we can use that to look up the exe easier.
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto id = package.Id();
|
||||
const std::wstring pfn{ id.FamilyName() };
|
||||
if (!pfn.empty())
|
||||
{
|
||||
const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW<std::wstring>(LocalAppDataAppsPath.data()) };
|
||||
const std::filesystem::path wtPath = windowsAppsPath / pfn / (IsDevBuild() ? WtdExe : WtExe);
|
||||
return wtPath;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
// If we're here, then we couldn't resolve our exe from the package. This
|
||||
// means we're running unpackaged. We should just use the
|
||||
// WindowsTerminal.exe that's sitting in the directory next to us.
|
||||
try
|
||||
{
|
||||
HMODULE hModule = GetModuleHandle(nullptr);
|
||||
THROW_LAST_ERROR_IF(hModule == nullptr);
|
||||
std::wstring dllPathString;
|
||||
THROW_IF_FAILED(wil::GetModuleFileNameW(hModule, dllPathString));
|
||||
const std::filesystem::path dllPath{ dllPathString };
|
||||
const std::filesystem::path rootDir = dllPath.parent_path();
|
||||
std::filesystem::path wtPath = rootDir / WindowsTerminalExe;
|
||||
return wtPath;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
return L"wt.exe";
|
||||
}();
|
||||
return exePath;
|
||||
}
|
Loading…
Reference in a new issue