Merge pull request #664 from mitchellh/kam

xterm audit: ANSI modes 2 (KAM), 4 (INSERT), 12 (SRM)
This commit is contained in:
Mitchell Hashimoto
2023-10-12 19:43:01 -07:00
committed by GitHub
8 changed files with 215 additions and 1 deletions

View File

@ -145,6 +145,7 @@ const DerivedConfig = struct {
mouse_shift_capture: configpkg.MouseShiftCapture,
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
macos_option_as_alt: configpkg.OptionAsAlt,
vt_kam_allowed: bool,
window_padding_x: u32,
window_padding_y: u32,
@ -166,6 +167,7 @@ const DerivedConfig = struct {
.mouse_shift_capture = config.@"mouse-shift-capture",
.macos_non_native_fullscreen = config.@"macos-non-native-fullscreen",
.macos_option_as_alt = config.@"macos-option-as-alt",
.vt_kam_allowed = config.@"vt-kam-allowed",
.window_padding_x = config.@"window-padding-x",
.window_padding_y = config.@"window-padding-y",
@ -985,6 +987,13 @@ pub fn keyCallback(
if (consumed and performed) return true;
}
// If we allow KAM and KAM is enabled then we do nothing.
if (self.config.vt_kam_allowed) {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
if (self.io.terminal.modes.get(.disable_keyboard)) return true;
}
// If this input event has text, then we hide the mouse if configured.
if (self.config.mouse_hide_while_typing and
!self.mouse.hidden and

View File

@ -461,6 +461,14 @@ keybind: Keybinds = .{},
/// The default value is "16-bit".
@"osc-color-report-format": OSCColorReportFormat = .@"16-bit",
/// If true, allows the "KAM" mode (ANSI mode 2) to be used within
/// the terminal. KAM disables keyboard input at the request of the
/// application. This is not a common feature and is not recommended
/// to be enabled. This will not be documented further because
/// if you know you need KAM, you know. If you don't know if you
/// need KAM, you don't need it.
@"vt-kam-allowed": bool = false,
/// If anything other than false, fullscreen mode on macOS will not use the
/// native fullscreen, but make the window fullscreen without animations and
/// using a new space. It's faster than the native fullscreen mode since it

View File

@ -1573,6 +1573,11 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
// This is the index of the final copyable value that we need to copy.
const copyable_end = start + copyable - 1;
// If our last cell we're shifting is wide, then we need to clear
// it to be empty so we don't split the multi-cell char.
const cell = row.getCellPtr(copyable_end);
if (cell.attrs.wide) cell.char = 0;
// Shift count cells. We have to do this backwards since we're not
// allocated new space, otherwise we'll copy duplicates.
var i: usize = 0;
@ -3949,6 +3954,23 @@ test "Terminal: insertBlanks shift off screen" {
}
}
test "Terminal: insertBlanks split multi-cell character" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 10);
defer t.deinit(alloc);
for ("123") |c| try t.print(c);
try t.print('橋');
t.setCursorPos(1, 1);
t.insertBlanks(1);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings(" 123", str);
}
}
test "Terminal: insertBlanks inside left/right scroll region" {
const alloc = testing.allocator;
var t = try init(alloc, 10, 10);
@ -4073,6 +4095,24 @@ test "Terminal: insert mode with wide characters at end" {
}
}
test "Terminal: insert mode pushing off wide character" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 2);
defer t.deinit(alloc);
for ("123") |c| try t.print(c);
try t.print('😀'); // 0x1F600
t.modes.set(.insert, true);
t.setCursorPos(1, 1);
try t.print('X');
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("X123", str);
}
}
test "Terminal: cursorIsAtPrompt" {
const alloc = testing.allocator;
var t = try init(alloc, 3, 2);

View File

@ -147,6 +147,7 @@ pub fn modeFromInt(v: u16, ansi: bool) ?Mode {
}
fn entryForMode(comptime mode: Mode) ModeEntry {
@setEvalBranchQuota(10_000);
const name = @tagName(mode);
for (entries) |entry| {
if (std.mem.eql(u8, entry.name, name)) return entry;
@ -170,7 +171,9 @@ const ModeEntry = struct {
/// valuable to redocument them all here.
const entries: []const ModeEntry = &.{
// ANSI
.{ .name = "disable_keyboard", .value = 2, .ansi = true }, // KAM
.{ .name = "insert", .value = 4, .ansi = true },
.{ .name = "send_receive_mode", .value = 12, .ansi = true, .default = true }, // SRM
// DEC
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
@ -212,7 +215,7 @@ test modeFromInt {
try testing.expect(modeFromInt(4, true).? == .insert);
try testing.expect(modeFromInt(9, true) == null);
try testing.expect(modeFromInt(9, false).? == .mouse_event_x10);
try testing.expect(modeFromInt(12, true) == null);
try testing.expect(modeFromInt(14, true) == null);
}
test ModeState {

View File

@ -0,0 +1,89 @@
import VTMode from "@/components/VTMode";
# Insert
<VTMode value={4} ansi={true} />
When enabled, text is written to the cell under the cursor
and all existing content is shifted right. When disabled, text
overwrites existing content.
This mode is unset as part of both [full reset (RIS)](/vt/ris)
and [soft reset (DECSTR)](/vt/decstr).
If a multi-cell character (such as "橋") is shifted so that the cell is split
in half, the multi-cell character can either be clipped or erased.
This mode is typically disabled on terminal startup.
## Validation
### INSERT V-1: Simple Usage
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "123456"
printf "\033[1G"
printf "\033[4h"
printf "ABC"
```
```
|ABC123456_|
```
### INSERT V-2: Pushing Off the Screen Edge
```bash
cols=$(tput cols)
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[${cols}G"
printf "\033[6D"
printf "123456"
printf "\033[6D"
printf "\033[4h"
printf "ABC"
```
```
|____ABC1234|
```
### INSERT V-3: Writing on the Screen Edge
```bash
cols=$(tput cols)
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[${cols}G"
printf "\033[6D"
printf "123456"
printf "\033[1D"
printf "\033[4h"
printf "ABC"
```
```
|____12345AB|
|Cc_________|
```
### INSERT V-3: Splitting a Multi-Cell Character
```bash
cols=$(tput cols)
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[${cols}G"
printf "\033[6D"
printf "1234橋"
printf "\033[6D"
printf "\033[4h"
printf "A"
```
```
|_____A1234_|
```

View File

@ -0,0 +1,30 @@
import VTMode from "@/components/VTMode";
# Keyboard Action Mode (KAM)
<VTMode value={2} ansi={true} />
Disable all keyboard input.
This mode is unset as part of both [full reset (RIS)](/vt/ris)
and [soft reset (DECSTR)](/vt/decstr).
A poorly behaved terminal program can lock the terminal emulator
using this command. Terminal emulators should provide a mechanism
to reset this or outright disable it.
This mode is typically disabled on terminal startup.
## Validation
### KAM V-1: Disable Keyboard Input
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "Keyboard input is now disabled.\n"
printf "\033[2h"
sleep 5
printf "\033[2l"
printf "Keyboard input is re-enabled.\n"
```

View File

@ -0,0 +1,17 @@
import VTMode from "@/components/VTMode";
# Send-Receive Mode (SRM)
<VTMode value={12} ansi={true} />
If reset, characters entered by the keyboard are shown on the screen
as well as being sent to the running program. If set, keyboard input
is sent only to the running program and the running program can choose
whether it wants to echo it back.
This mode is typically enabled on terminal startup.
This mode is generally unsupported across most terminals today and
is recommended to be retired.[^1]
[^1]: https://gitlab.gnome.org/GNOME/vte/-/issues/69

View File

@ -0,0 +1,18 @@
export default function VTMode({
value,
ansi = false,
}: {
value: number;
ansi: boolean;
}) {
return (
<div className="flex my-2.5">
<div className="border px-1 grid grid-rows-2 grid-cols-1 text-center">
<div>
{ansi ? "" : "?"}
{value}
</div>
</div>
</div>
);
}