Skip to content

Commit

Permalink
Fixed #3799: Introduce sendInput command (#7249)
Browse files Browse the repository at this point in the history
## Summary of the Pull Request

This PR enables users to send arbitrary text input to the shell via a keybinding.

## PR Checklist
* [x] Closes #3799
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [x] Schema updated.
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #3799

## Detailed Description of the Pull Request / Additional comments

## Validation Steps Performed

Added the following keybindings:
```json
{ "keys": "p", "command": { "action": "sendInput", "input": "foobar" } },
{ "keys": "q", "command": { "action": "sendInput", "input": "\u001b[A" } },
```
Ensured that when pressing <kbd>P</kbd> "foobar" is echoed to the shell and when pressing <kbd>Q</kbd> the shell history is being navigated backwards.
  • Loading branch information
lhecker committed Aug 12, 2020
1 parent a34cfa4 commit a2721c1
Show file tree
Hide file tree
Showing 21 changed files with 175 additions and 29 deletions.
1 change: 1 addition & 0 deletions doc/cascadia/SettingsSchema.md
Expand Up @@ -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.<br>ANSI escape sequences may be used. Escape codes like `\x1b` must be written as `\u001b`.<br>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`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile`<br>7. `splitMode` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string<br>7. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name.<br>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. | | | |
Expand Down
24 changes: 24 additions & 0 deletions doc/cascadia/profiles.schema.json
Expand Up @@ -52,6 +52,7 @@
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"sendInput",
"splitPane",
"switchToTab",
"toggleFocusMode",
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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" },
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalApp/ActionAndArgs.cpp
Expand Up @@ -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" };
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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"" },
Expand Down
13 changes: 13 additions & 0 deletions src/cascadia/TerminalApp/ActionArgs.cpp
Expand Up @@ -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"
Expand All @@ -21,6 +22,8 @@
#include "ExecuteCommandlineArgs.g.cpp"
#include "ToggleTabSwitcherArgs.g.h"

#include "Utils.h"

#include <LibraryResources.h>

namespace winrt::TerminalApp::implementation
Expand Down Expand Up @@ -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:
Expand Down
32 changes: 32 additions & 0 deletions src/cascadia/TerminalApp/ActionArgs.h
Expand Up @@ -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"
Expand Down Expand Up @@ -270,6 +271,37 @@ namespace winrt::TerminalApp::implementation
}
};

struct SendInputArgs : public SendInputArgsT<SendInputArgs>
{
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<SendInputArgs>(); 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<SendInputArgs>();
JsonUtils::GetValueForKey(json, InputKey, args->_Input);
if (args->_Input.empty())
{
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
}
return { *args, {} };
}
};

struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
{
SplitPaneArgs() = default;
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalApp/ActionArgs.idl
Expand Up @@ -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; };
Expand Down
15 changes: 15 additions & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Expand Up @@ -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<TerminalApp::SendInputArgs>())
{
const auto termControl = _GetActiveControl();
termControl.SendInput(realArgs.Input());
args.Handled(true);
}
}

void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
Expand Down
26 changes: 3 additions & 23 deletions src/cascadia/TerminalApp/DebugTapConnection.cpp
Expand Up @@ -3,6 +3,7 @@

#include "pch.h"
#include "DebugTapConnection.h"
#include "Utils.h"

using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::winrt::Windows::Foundation;
Expand Down Expand Up @@ -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<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
_TerminalOutputHandlers(formatted);
}
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Expand Up @@ -449,6 +449,10 @@
<data name="NewTabCommandKey" xml:space="preserve">
<value>New tab</value>
</data>
<data name="SendInputCommandKey" xml:space="preserve">
<value>Send Input: "{0}"</value>
<comment>{0} will be replaced with a string of input as defined by the user</comment>
</data>
<data name="SplitPaneCommandKey" xml:space="preserve">
<value>Split pane</value>
</data>
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalApp/ShortcutActionDispatch.cpp
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/ShortcutActionDispatch.h
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/ShortcutActionDispatch.idl
Expand Up @@ -20,6 +20,7 @@ namespace TerminalApp
PrevTab,
SplitVertical,
SplitHorizontal,
SendInput,
SplitPane,
TogglePaneZoom,
SwitchToTab,
Expand Down Expand Up @@ -71,6 +72,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> SwitchToTab;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> NextTab;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> PrevTab;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> SendInput;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> SplitPane;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> TogglePaneZoom;
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> AdjustFontSize;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -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 });
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Expand Up @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions 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;
}
7 changes: 7 additions & 0 deletions src/cascadia/TerminalApp/Utils.h
Expand Up @@ -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 });
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj
Expand Up @@ -189,6 +189,7 @@
<ClCompile Include="../Pane.LayoutSizeNode.cpp" />
<ClCompile Include="../ColorHelper.cpp" />
<ClCompile Include="../DebugTapConnection.cpp" />
<ClCompile Include="../Utils.cpp" />
<ClCompile Include="../TerminalSettings.cpp">
<DependentUpon>../TerminalSettings.idl</DependentUpon>
</ClCompile>
Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters
Expand Up @@ -59,6 +59,7 @@
<ClCompile Include="../Commandline.cpp" />
<ClCompile Include="../ColorHelper.cpp" />
<ClCompile Include="../DebugTapConnection.cpp" />
<ClCompile Include="../Utils.cpp" />
<ClCompile Include="../TerminalSettings.cpp">
<Filter>settings</Filter>
</ClCompile>
Expand Down Expand Up @@ -202,4 +203,4 @@
<Filter>app</Filter>
</ApplicationDefinition>
</ItemGroup>
</Project>
</Project>

0 comments on commit a2721c1

Please sign in to comment.