diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index 9c58edc89f4..66e5cb67113 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -142,6 +142,7 @@ For commands with arguments: | `scrollUp` | Move the screen up. | | | | | `scrollUpPage` | Move the screen up a whole page. | | | | | `scrollDownPage` | Move the screen down a whole page. | | | | +| `sendInput` | Sends some text input to the shell. | `input` | string | The text input to feed into the shell.
ANSI escape sequences may be used. Escape codes like `\x1b` must be written as `\u001b`.
For instance the input `"text\n"` will write "text" followed by a newline. `"\u001b[D"` will behave as if the left arrow button had been pressed. | | `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*
2. `commandLine`
3. `startingDirectory`
4. `tabTitle`
5. `index`
6. `profile`
7. `splitMode` | 1. `vertical`, `horizontal`, `auto`
2. string
3. string
4. string
5. integer
6. string
7. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.
2. Executable run within the pane.
3. Directory in which the pane will open.
4. Title of the tab when the new pane is focused.
5. Profile that will open based on its position in the dropdown (starting at 0).
6. Profile that will open based on its GUID or name.
7. Controls how the pane splits. Only accepts `duplicate` which will duplicate the focused pane's profile into a new pane. | | `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). | | `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | | diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index f1e1843bf96..e2253724a54 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -52,6 +52,7 @@ "scrollDownPage", "scrollUp", "scrollUpPage", + "sendInput", "splitPane", "switchToTab", "toggleFocusMode", @@ -230,6 +231,28 @@ ], "required": [ "direction" ] }, + "SendInputAction": { + "description": "Arguments corresponding to a Send Input Action", + "allOf": [ + { + "$ref": "#/definitions/ShortcutAction" + }, + { + "properties": { + "action": { + "type": "string", + "pattern": "sendInput" + }, + "input": { + "type": "string", + "default": "", + "description": "The text input to feed into the shell. ANSI escape sequences may be used. Escape codes like \\x1b must be written as \\u001b." + } + } + } + ], + "required": [ "input" ] + }, "SplitPaneAction": { "description": "Arguments corresponding to a Split Pane Action", "allOf": [ @@ -390,6 +413,7 @@ { "$ref": "#/definitions/SwitchToTabAction" }, { "$ref": "#/definitions/MoveFocusAction" }, { "$ref": "#/definitions/ResizePaneAction" }, + { "$ref": "#/definitions/SendInputAction" }, { "$ref": "#/definitions/SplitPaneAction" }, { "$ref": "#/definitions/OpenSettingsAction" }, { "$ref": "#/definitions/SetTabColorAction" }, diff --git a/src/cascadia/TerminalApp/ActionAndArgs.cpp b/src/cascadia/TerminalApp/ActionAndArgs.cpp index fadadac3b24..150a31c92ea 100644 --- a/src/cascadia/TerminalApp/ActionAndArgs.cpp +++ b/src/cascadia/TerminalApp/ActionAndArgs.cpp @@ -27,6 +27,7 @@ static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" }; static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" }; static constexpr std::string_view SwitchToTabKey{ "switchToTab" }; static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings +static constexpr std::string_view SendInputKey{ "sendInput" }; static constexpr std::string_view SplitPaneKey{ "splitPane" }; static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" }; static constexpr std::string_view ResizePaneKey{ "resizePane" }; @@ -90,6 +91,7 @@ namespace winrt::TerminalApp::implementation { ToggleFocusModeKey, ShortcutAction::ToggleFocusMode }, { ToggleFullscreenKey, ShortcutAction::ToggleFullscreen }, { ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop }, + { SendInputKey, ShortcutAction::SendInput }, { SplitPaneKey, ShortcutAction::SplitPane }, { TogglePaneZoomKey, ShortcutAction::TogglePaneZoom }, { SetTabColorKey, ShortcutAction::SetTabColor }, @@ -125,6 +127,8 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson }, + { ShortcutAction::SendInput, winrt::TerminalApp::implementation::SendInputArgs::FromJson }, + { ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson }, { ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson }, @@ -284,6 +288,7 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") }, { ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") }, { ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") }, + { ShortcutAction::SendInput, L"" }, { ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") }, { ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") }, { ShortcutAction::Invalid, L"" }, diff --git a/src/cascadia/TerminalApp/ActionArgs.cpp b/src/cascadia/TerminalApp/ActionArgs.cpp index d6e7219fd13..1bc3983481d 100644 --- a/src/cascadia/TerminalApp/ActionArgs.cpp +++ b/src/cascadia/TerminalApp/ActionArgs.cpp @@ -13,6 +13,7 @@ #include "ResizePaneArgs.g.cpp" #include "MoveFocusArgs.g.cpp" #include "AdjustFontSizeArgs.g.cpp" +#include "SendInputArgs.g.cpp" #include "SplitPaneArgs.g.cpp" #include "OpenSettingsArgs.g.cpp" #include "SetColorSchemeArgs.g.cpp" @@ -21,6 +22,8 @@ #include "ExecuteCommandlineArgs.g.cpp" #include "ToggleTabSwitcherArgs.g.h" +#include "Utils.h" + #include namespace winrt::TerminalApp::implementation @@ -167,6 +170,16 @@ namespace winrt::TerminalApp::implementation } } + winrt::hstring SendInputArgs::GenerateName() const + { + // The string will be similar to the following: + // * "Send Input: ...input..." + + auto escapedInput = VisualizeControlCodes(_Input); + auto name = fmt::format(std::wstring_view(RS_(L"SendInputCommandKey")), escapedInput); + return winrt::hstring{ name }; + } + winrt::hstring SplitPaneArgs::GenerateName() const { // The string will be similar to the following: diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h index 7c768d58054..d52cc3f4494 100644 --- a/src/cascadia/TerminalApp/ActionArgs.h +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -13,6 +13,7 @@ #include "ResizePaneArgs.g.h" #include "MoveFocusArgs.g.h" #include "AdjustFontSizeArgs.g.h" +#include "SendInputArgs.g.h" #include "SplitPaneArgs.g.h" #include "OpenSettingsArgs.g.h" #include "SetColorSchemeArgs.g.h" @@ -270,6 +271,37 @@ namespace winrt::TerminalApp::implementation } }; + struct SendInputArgs : public SendInputArgsT + { + SendInputArgs() = default; + GETSET_PROPERTY(winrt::hstring, Input, L""); + + static constexpr std::string_view InputKey{ "input" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + if (auto otherAsUs = other.try_as(); otherAsUs) + { + return otherAsUs->_Input == _Input; + } + 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(); + JsonUtils::GetValueForKey(json, InputKey, args->_Input); + if (args->_Input.empty()) + { + return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } }; + } + return { *args, {} }; + } + }; + struct SplitPaneArgs : public SplitPaneArgsT { SplitPaneArgs() = default; diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index a460d712a92..ee50771eb94 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -94,6 +94,11 @@ namespace TerminalApp Int32 Delta { get; }; }; + [default_interface] runtimeclass SendInputArgs : IActionArgs + { + String Input { get; }; + }; + [default_interface] runtimeclass SplitPaneArgs : IActionArgs { SplitState SplitStyle { get; }; diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 19a8c44f2ad..80a06e733de 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -89,6 +89,21 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } + void TerminalPage::_HandleSendInput(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (args == nullptr) + { + args.Handled(false); + } + else if (const auto& realArgs = args.ActionArgs().try_as()) + { + const auto termControl = _GetActiveControl(); + termControl.SendInput(realArgs.Input()); + args.Handled(true); + } + } + void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp index 21994920ecd..d149ad46213 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.cpp +++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "DebugTapConnection.h" +#include "Utils.h" using namespace ::winrt::Microsoft::Terminal::TerminalConnection; using namespace ::winrt::Windows::Foundation; @@ -91,36 +92,15 @@ namespace winrt::Microsoft::TerminalApp::implementation return ConnectionState::Failed; } - static std::wstring _sanitizeString(const std::wstring_view str) - { - std::wstring newString{ str.begin(), str.end() }; - for (auto& ch : newString) - { - if (ch < 0x20) - { - ch += 0x2400; - } - else if (ch == 0x20) - { - ch = 0x2423; // replace space with ␣ - } - else if (ch == 0x7f) - { - ch = 0x2421; // replace del with ␡ - } - } - return newString; - } - void DebugTapConnection::_OutputHandler(const hstring str) { - _TerminalOutputHandlers(_sanitizeString(str)); + _TerminalOutputHandlers(VisualizeControlCodes(str)); } // Called by the DebugInputTapConnection to print user input void DebugTapConnection::_PrintInput(const hstring& str) { - auto clean{ _sanitizeString(str) }; + auto clean{ VisualizeControlCodes(str) }; auto formatted{ wil::str_printf(L"\x1b[91m%ls\x1b[m", clean.data()) }; _TerminalOutputHandlers(formatted); } diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index c891d53df6a..c8951fac9f2 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -449,6 +449,10 @@ New tab + + Send Input: "{0}" + {0} will be replaced with a string of input as defined by the user + Split pane diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index e8b9eb778c4..20a43791c2d 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -113,6 +113,12 @@ namespace winrt::TerminalApp::implementation break; } + case ShortcutAction::SendInput: + { + _SendInputHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::SplitVertical: case ShortcutAction::SplitHorizontal: case ShortcutAction::SplitPane: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index fa139848e63..9da3aadcdf2 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -35,6 +35,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(SendInput, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(TogglePaneZoom, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index b0341d4bf81..e13c0433565 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -20,6 +20,7 @@ namespace TerminalApp PrevTab, SplitVertical, SplitHorizontal, + SendInput, SplitPane, TogglePaneZoom, SwitchToTab, @@ -71,6 +72,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SwitchToTab; event Windows.Foundation.TypedEventHandler NextTab; event Windows.Foundation.TypedEventHandler PrevTab; + event Windows.Foundation.TypedEventHandler SendInput; event Windows.Foundation.TypedEventHandler SplitPane; event Windows.Foundation.TypedEventHandler TogglePaneZoom; event Windows.Foundation.TypedEventHandler AdjustFontSize; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 3838e4ccf50..22547b2e005 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -889,6 +889,7 @@ namespace winrt::TerminalApp::implementation _actionDispatch->ScrollDown({ this, &TerminalPage::_HandleScrollDown }); _actionDispatch->NextTab({ this, &TerminalPage::_HandleNextTab }); _actionDispatch->PrevTab({ this, &TerminalPage::_HandlePrevTab }); + _actionDispatch->SendInput({ this, &TerminalPage::_HandleSendInput }); _actionDispatch->SplitPane({ this, &TerminalPage::_HandleSplitPane }); _actionDispatch->TogglePaneZoom({ this, &TerminalPage::_HandleTogglePaneZoom }); _actionDispatch->ScrollUpPage({ this, &TerminalPage::_HandleScrollUpPage }); diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 984f1064245..d89f96ca931 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -203,6 +203,7 @@ namespace winrt::TerminalApp::implementation void _HandleScrollDown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleNextTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandlePrevTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleSendInput(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleSplitPane(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleTogglePaneZoom(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleScrollUpPage(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); diff --git a/src/cascadia/TerminalApp/Utils.cpp b/src/cascadia/TerminalApp/Utils.cpp new file mode 100644 index 00000000000..46f07b9bd93 --- /dev/null +++ b/src/cascadia/TerminalApp/Utils.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. + +#include "pch.h" +#include "Utils.h" + +std::wstring VisualizeControlCodes(std::wstring str) noexcept +{ + for (auto& ch : str) + { + if (ch < 0x20) + { + ch += 0x2400; + } + else if (ch == 0x20) + { + ch = 0x2423; // replace space with ␣ + } + else if (ch == 0x7f) + { + ch = 0x2421; // replace del with ␡ + } + } + return str; +} diff --git a/src/cascadia/TerminalApp/Utils.h b/src/cascadia/TerminalApp/Utils.h index cb23bac450d..91915804139 100644 --- a/src/cascadia/TerminalApp/Utils.h +++ b/src/cascadia/TerminalApp/Utils.h @@ -115,3 +115,10 @@ TIconSource GetColoredIcon(const winrt::hstring& path) return nullptr; } + +std::wstring VisualizeControlCodes(std::wstring str) noexcept; + +inline std::wstring VisualizeControlCodes(std::wstring_view str) noexcept +{ + return VisualizeControlCodes(std::wstring{ str }); +} diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj index f3c98e58e4e..aecf0ef0847 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj @@ -189,6 +189,7 @@ + ../TerminalSettings.idl diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters index fea379bc66c..9a80a146c43 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters @@ -59,6 +59,7 @@ + settings @@ -202,4 +203,4 @@ app - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 245413fe75c..058e478bc72 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -104,8 +104,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation }; _connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn); - auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1); - _terminal->SetWriteInputCallback(inputFn); + _terminal->SetWriteInputCallback([this](std::wstring& wstr) { + _SendInputToConnection(wstr); + }); _terminal->UpdateSettings(settings); @@ -296,6 +297,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } } + + // Method Description: + // - Writes the given sequence as input to the active terminal connection, + // Arguments: + // - wstr: the string of characters to write to the terminal connection. + // Return Value: + // - + void TermControl::SendInput(const winrt::hstring& wstr) + { + _SendInputToConnection(wstr); + } + void TermControl::ToggleRetroEffect() { auto lock = _terminal->LockForWriting(); @@ -1764,12 +1777,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } // Method Description: - // - Writes the given sequence as input to the active terminal connection, + // - Writes the given sequence as input to the active terminal connection. + // - This method has been overloaded to allow zero-copy winrt::param::hstring optimizations. // Arguments: // - wstr: the string of characters to write to the terminal connection. // Return Value: // - - void TermControl::_SendInputToConnection(const std::wstring& wstr) + void TermControl::_SendInputToConnection(const winrt::hstring& wstr) + { + _connection.WriteInput(wstr); + } + + void TermControl::_SendInputToConnection(std::wstring_view wstr) { _connection.WriteInput(wstr); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index a3f26712962..b34ea7b813c 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -81,6 +81,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void AdjustFontSize(int fontSizeDelta); void ResetFontSize(); + void SendInput(const winrt::hstring& input); void ToggleRetroEffect(); winrt::fire_and_forget RenderEngineSwapChainChanged(); @@ -221,7 +222,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition); - void _SendInputToConnection(const std::wstring& wstr); + void _SendInputToConnection(const winrt::hstring& wstr); + void _SendInputToConnection(std::wstring_view wstr); void _SendPastedTextToConnection(const std::wstring& wstr); void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e); void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 85b6e602cfd..4e3eefa0c44 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -71,6 +71,7 @@ namespace Microsoft.Terminal.TerminalControl void AdjustFontSize(Int32 fontSizeDelta); void ResetFontSize(); + void SendInput(String input); void ToggleRetroEffect(); Windows.Foundation.IReference TabColor { get; };