mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #664 from mitchellh/kam
xterm audit: ANSI modes 2 (KAM), 4 (INSERT), 12 (SRM)
This commit is contained in:
@ -145,6 +145,7 @@ const DerivedConfig = struct {
|
|||||||
mouse_shift_capture: configpkg.MouseShiftCapture,
|
mouse_shift_capture: configpkg.MouseShiftCapture,
|
||||||
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
|
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
|
||||||
macos_option_as_alt: configpkg.OptionAsAlt,
|
macos_option_as_alt: configpkg.OptionAsAlt,
|
||||||
|
vt_kam_allowed: bool,
|
||||||
window_padding_x: u32,
|
window_padding_x: u32,
|
||||||
window_padding_y: u32,
|
window_padding_y: u32,
|
||||||
|
|
||||||
@ -166,6 +167,7 @@ const DerivedConfig = struct {
|
|||||||
.mouse_shift_capture = config.@"mouse-shift-capture",
|
.mouse_shift_capture = config.@"mouse-shift-capture",
|
||||||
.macos_non_native_fullscreen = config.@"macos-non-native-fullscreen",
|
.macos_non_native_fullscreen = config.@"macos-non-native-fullscreen",
|
||||||
.macos_option_as_alt = config.@"macos-option-as-alt",
|
.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_x = config.@"window-padding-x",
|
||||||
.window_padding_y = config.@"window-padding-y",
|
.window_padding_y = config.@"window-padding-y",
|
||||||
|
|
||||||
@ -985,6 +987,13 @@ pub fn keyCallback(
|
|||||||
if (consumed and performed) return true;
|
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 this input event has text, then we hide the mouse if configured.
|
||||||
if (self.config.mouse_hide_while_typing and
|
if (self.config.mouse_hide_while_typing and
|
||||||
!self.mouse.hidden and
|
!self.mouse.hidden and
|
||||||
|
@ -461,6 +461,14 @@ keybind: Keybinds = .{},
|
|||||||
/// The default value is "16-bit".
|
/// The default value is "16-bit".
|
||||||
@"osc-color-report-format": OSCColorReportFormat = .@"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
|
/// If anything other than false, fullscreen mode on macOS will not use the
|
||||||
/// native fullscreen, but make the window fullscreen without animations and
|
/// native fullscreen, but make the window fullscreen without animations and
|
||||||
/// using a new space. It's faster than the native fullscreen mode since it
|
/// using a new space. It's faster than the native fullscreen mode since it
|
||||||
|
@ -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.
|
// This is the index of the final copyable value that we need to copy.
|
||||||
const copyable_end = start + copyable - 1;
|
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
|
// Shift count cells. We have to do this backwards since we're not
|
||||||
// allocated new space, otherwise we'll copy duplicates.
|
// allocated new space, otherwise we'll copy duplicates.
|
||||||
var i: usize = 0;
|
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" {
|
test "Terminal: insertBlanks inside left/right scroll region" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var t = try init(alloc, 10, 10);
|
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" {
|
test "Terminal: cursorIsAtPrompt" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var t = try init(alloc, 3, 2);
|
var t = try init(alloc, 3, 2);
|
||||||
|
@ -147,6 +147,7 @@ pub fn modeFromInt(v: u16, ansi: bool) ?Mode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn entryForMode(comptime mode: Mode) ModeEntry {
|
fn entryForMode(comptime mode: Mode) ModeEntry {
|
||||||
|
@setEvalBranchQuota(10_000);
|
||||||
const name = @tagName(mode);
|
const name = @tagName(mode);
|
||||||
for (entries) |entry| {
|
for (entries) |entry| {
|
||||||
if (std.mem.eql(u8, entry.name, name)) return entry;
|
if (std.mem.eql(u8, entry.name, name)) return entry;
|
||||||
@ -170,7 +171,9 @@ const ModeEntry = struct {
|
|||||||
/// valuable to redocument them all here.
|
/// valuable to redocument them all here.
|
||||||
const entries: []const ModeEntry = &.{
|
const entries: []const ModeEntry = &.{
|
||||||
// ANSI
|
// ANSI
|
||||||
|
.{ .name = "disable_keyboard", .value = 2, .ansi = true }, // KAM
|
||||||
.{ .name = "insert", .value = 4, .ansi = true },
|
.{ .name = "insert", .value = 4, .ansi = true },
|
||||||
|
.{ .name = "send_receive_mode", .value = 12, .ansi = true, .default = true }, // SRM
|
||||||
|
|
||||||
// DEC
|
// DEC
|
||||||
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
|
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
|
||||||
@ -212,7 +215,7 @@ test modeFromInt {
|
|||||||
try testing.expect(modeFromInt(4, true).? == .insert);
|
try testing.expect(modeFromInt(4, true).? == .insert);
|
||||||
try testing.expect(modeFromInt(9, true) == null);
|
try testing.expect(modeFromInt(9, true) == null);
|
||||||
try testing.expect(modeFromInt(9, false).? == .mouse_event_x10);
|
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 {
|
test ModeState {
|
||||||
|
89
website/app/vt/modes/insert/page.mdx
Normal file
89
website/app/vt/modes/insert/page.mdx
Normal 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_|
|
||||||
|
```
|
30
website/app/vt/modes/kam/page.mdx
Normal file
30
website/app/vt/modes/kam/page.mdx
Normal 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"
|
||||||
|
```
|
17
website/app/vt/modes/srm/page.mdx
Normal file
17
website/app/vt/modes/srm/page.mdx
Normal 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
|
18
website/components/VTMode.tsx
Normal file
18
website/components/VTMode.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user