Skip to content

Commit a2721c1

Browse files
authored
Fixed microsoft#3799: Introduce sendInput command (microsoft#7249)
## Summary of the Pull Request This PR enables users to send arbitrary text input to the shell via a keybinding. ## PR Checklist * [x] Closes microsoft#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: microsoft#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.
1 parent a34cfa4 commit a2721c1

21 files changed

+175
-29
lines changed

doc/cascadia/SettingsSchema.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ For commands with arguments:
142142
| `scrollUp` | Move the screen up. | | | |
143143
| `scrollUpPage` | Move the screen up a whole page. | | | |
144144
| `scrollDownPage` | Move the screen down a whole page. | | | |
145+
| `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. |
145146
| `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. |
146147
| `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). |
147148
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |

doc/cascadia/profiles.schema.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"scrollDownPage",
5353
"scrollUp",
5454
"scrollUpPage",
55+
"sendInput",
5556
"splitPane",
5657
"switchToTab",
5758
"toggleFocusMode",
@@ -230,6 +231,28 @@
230231
],
231232
"required": [ "direction" ]
232233
},
234+
"SendInputAction": {
235+
"description": "Arguments corresponding to a Send Input Action",
236+
"allOf": [
237+
{
238+
"$ref": "#/definitions/ShortcutAction"
239+
},
240+
{
241+
"properties": {
242+
"action": {
243+
"type": "string",
244+
"pattern": "sendInput"
245+
},
246+
"input": {
247+
"type": "string",
248+
"default": "",
249+
"description": "The text input to feed into the shell. ANSI escape sequences may be used. Escape codes like \\x1b must be written as \\u001b."
250+
}
251+
}
252+
}
253+
],
254+
"required": [ "input" ]
255+
},
233256
"SplitPaneAction": {
234257
"description": "Arguments corresponding to a Split Pane Action",
235258
"allOf": [
@@ -390,6 +413,7 @@
390413
{ "$ref": "#/definitions/SwitchToTabAction" },
391414
{ "$ref": "#/definitions/MoveFocusAction" },
392415
{ "$ref": "#/definitions/ResizePaneAction" },
416+
{ "$ref": "#/definitions/SendInputAction" },
393417
{ "$ref": "#/definitions/SplitPaneAction" },
394418
{ "$ref": "#/definitions/OpenSettingsAction" },
395419
{ "$ref": "#/definitions/SetTabColorAction" },

src/cascadia/TerminalApp/ActionAndArgs.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
2727
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
2828
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
2929
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings
30+
static constexpr std::string_view SendInputKey{ "sendInput" };
3031
static constexpr std::string_view SplitPaneKey{ "splitPane" };
3132
static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" };
3233
static constexpr std::string_view ResizePaneKey{ "resizePane" };
@@ -90,6 +91,7 @@ namespace winrt::TerminalApp::implementation
9091
{ ToggleFocusModeKey, ShortcutAction::ToggleFocusMode },
9192
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
9293
{ ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop },
94+
{ SendInputKey, ShortcutAction::SendInput },
9395
{ SplitPaneKey, ShortcutAction::SplitPane },
9496
{ TogglePaneZoomKey, ShortcutAction::TogglePaneZoom },
9597
{ SetTabColorKey, ShortcutAction::SetTabColor },
@@ -125,6 +127,8 @@ namespace winrt::TerminalApp::implementation
125127

126128
{ ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson },
127129

130+
{ ShortcutAction::SendInput, winrt::TerminalApp::implementation::SendInputArgs::FromJson },
131+
128132
{ ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson },
129133

130134
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
@@ -284,6 +288,7 @@ namespace winrt::TerminalApp::implementation
284288
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
285289
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
286290
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
291+
{ ShortcutAction::SendInput, L"" },
287292
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
288293
{ ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") },
289294
{ ShortcutAction::Invalid, L"" },

src/cascadia/TerminalApp/ActionArgs.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ResizePaneArgs.g.cpp"
1414
#include "MoveFocusArgs.g.cpp"
1515
#include "AdjustFontSizeArgs.g.cpp"
16+
#include "SendInputArgs.g.cpp"
1617
#include "SplitPaneArgs.g.cpp"
1718
#include "OpenSettingsArgs.g.cpp"
1819
#include "SetColorSchemeArgs.g.cpp"
@@ -21,6 +22,8 @@
2122
#include "ExecuteCommandlineArgs.g.cpp"
2223
#include "ToggleTabSwitcherArgs.g.h"
2324

25+
#include "Utils.h"
26+
2427
#include <LibraryResources.h>
2528

2629
namespace winrt::TerminalApp::implementation
@@ -167,6 +170,16 @@ namespace winrt::TerminalApp::implementation
167170
}
168171
}
169172

173+
winrt::hstring SendInputArgs::GenerateName() const
174+
{
175+
// The string will be similar to the following:
176+
// * "Send Input: ...input..."
177+
178+
auto escapedInput = VisualizeControlCodes(_Input);
179+
auto name = fmt::format(std::wstring_view(RS_(L"SendInputCommandKey")), escapedInput);
180+
return winrt::hstring{ name };
181+
}
182+
170183
winrt::hstring SplitPaneArgs::GenerateName() const
171184
{
172185
// The string will be similar to the following:

src/cascadia/TerminalApp/ActionArgs.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ResizePaneArgs.g.h"
1414
#include "MoveFocusArgs.g.h"
1515
#include "AdjustFontSizeArgs.g.h"
16+
#include "SendInputArgs.g.h"
1617
#include "SplitPaneArgs.g.h"
1718
#include "OpenSettingsArgs.g.h"
1819
#include "SetColorSchemeArgs.g.h"
@@ -270,6 +271,37 @@ namespace winrt::TerminalApp::implementation
270271
}
271272
};
272273

274+
struct SendInputArgs : public SendInputArgsT<SendInputArgs>
275+
{
276+
SendInputArgs() = default;
277+
GETSET_PROPERTY(winrt::hstring, Input, L"");
278+
279+
static constexpr std::string_view InputKey{ "input" };
280+
281+
public:
282+
hstring GenerateName() const;
283+
284+
bool Equals(const IActionArgs& other)
285+
{
286+
if (auto otherAsUs = other.try_as<SendInputArgs>(); otherAsUs)
287+
{
288+
return otherAsUs->_Input == _Input;
289+
}
290+
return false;
291+
};
292+
static FromJsonResult FromJson(const Json::Value& json)
293+
{
294+
// LOAD BEARING: Not using make_self here _will_ break you in the future!
295+
auto args = winrt::make_self<SendInputArgs>();
296+
JsonUtils::GetValueForKey(json, InputKey, args->_Input);
297+
if (args->_Input.empty())
298+
{
299+
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
300+
}
301+
return { *args, {} };
302+
}
303+
};
304+
273305
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
274306
{
275307
SplitPaneArgs() = default;

src/cascadia/TerminalApp/ActionArgs.idl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ namespace TerminalApp
9494
Int32 Delta { get; };
9595
};
9696

97+
[default_interface] runtimeclass SendInputArgs : IActionArgs
98+
{
99+
String Input { get; };
100+
};
101+
97102
[default_interface] runtimeclass SplitPaneArgs : IActionArgs
98103
{
99104
SplitState SplitStyle { get; };

src/cascadia/TerminalApp/AppActionHandlers.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@ namespace winrt::TerminalApp::implementation
8989
args.Handled(true);
9090
}
9191

92+
void TerminalPage::_HandleSendInput(const IInspectable& /*sender*/,
93+
const TerminalApp::ActionEventArgs& args)
94+
{
95+
if (args == nullptr)
96+
{
97+
args.Handled(false);
98+
}
99+
else if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::SendInputArgs>())
100+
{
101+
const auto termControl = _GetActiveControl();
102+
termControl.SendInput(realArgs.Input());
103+
args.Handled(true);
104+
}
105+
}
106+
92107
void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/,
93108
const TerminalApp::ActionEventArgs& args)
94109
{

src/cascadia/TerminalApp/DebugTapConnection.cpp

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "pch.h"
55
#include "DebugTapConnection.h"
6+
#include "Utils.h"
67

78
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
89
using namespace ::winrt::Windows::Foundation;
@@ -91,36 +92,15 @@ namespace winrt::Microsoft::TerminalApp::implementation
9192
return ConnectionState::Failed;
9293
}
9394

94-
static std::wstring _sanitizeString(const std::wstring_view str)
95-
{
96-
std::wstring newString{ str.begin(), str.end() };
97-
for (auto& ch : newString)
98-
{
99-
if (ch < 0x20)
100-
{
101-
ch += 0x2400;
102-
}
103-
else if (ch == 0x20)
104-
{
105-
ch = 0x2423; // replace space with ␣
106-
}
107-
else if (ch == 0x7f)
108-
{
109-
ch = 0x2421; // replace del with ␡
110-
}
111-
}
112-
return newString;
113-
}
114-
11595
void DebugTapConnection::_OutputHandler(const hstring str)
11696
{
117-
_TerminalOutputHandlers(_sanitizeString(str));
97+
_TerminalOutputHandlers(VisualizeControlCodes(str));
11898
}
11999

120100
// Called by the DebugInputTapConnection to print user input
121101
void DebugTapConnection::_PrintInput(const hstring& str)
122102
{
123-
auto clean{ _sanitizeString(str) };
103+
auto clean{ VisualizeControlCodes(str) };
124104
auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
125105
_TerminalOutputHandlers(formatted);
126106
}

src/cascadia/TerminalApp/Resources/en-US/Resources.resw

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,10 @@
449449
<data name="NewTabCommandKey" xml:space="preserve">
450450
<value>New tab</value>
451451
</data>
452+
<data name="SendInputCommandKey" xml:space="preserve">
453+
<value>Send Input: "{0}"</value>
454+
<comment>{0} will be replaced with a string of input as defined by the user</comment>
455+
</data>
452456
<data name="SplitPaneCommandKey" xml:space="preserve">
453457
<value>Split pane</value>
454458
</data>

src/cascadia/TerminalApp/ShortcutActionDispatch.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ namespace winrt::TerminalApp::implementation
113113
break;
114114
}
115115

116+
case ShortcutAction::SendInput:
117+
{
118+
_SendInputHandlers(*this, *eventArgs);
119+
break;
120+
}
121+
116122
case ShortcutAction::SplitVertical:
117123
case ShortcutAction::SplitHorizontal:
118124
case ShortcutAction::SplitPane:

0 commit comments

Comments
 (0)