控制台虚拟终端序列

虚拟终端序列是控制字符序列,可在写入输出流时控制游标移动、控制台颜色和其他操作。 在输入流上也可以接收序列,以响应输出流查询信息序列,或在设置适当模式时作为用户输入的编码。

可以使用 GetConsoleModeSetConsoleMode 函数配置此行为。 本文档末尾包含了建议的启用虚拟终端行为的方法的示例。

以下序列的行为基于 VT100 和派生终端仿真器技术,尤其是 xterm 终端仿真器。 有关终端序列的详细信息,请访问 http://vt100.nethttp://invisible-island.net/xterm/ctlseqs/ctlseqs.html

输出序列

如果在屏幕缓冲区句柄上使用 SetConsoleMode 函数设置了 ENABLE_VIRTUAL_TERMINAL_PROCESSING 标志,控制台主机会在写入输出流时截获以下终端序列。 请注意,DISABLE_NEWLINE_AUTO_RETURN 标志在模拟其他终端仿真器相对于写入任意行中最后一列的字符的游标定位和滚动行为时也很有用。

简单光标定位

在以下所有描述中,ESC 始终为十六进制值 0x1B。 终端序列中不包含空格。 单个终端序列可以在任意字符或字节位置拆分到对 WriteFileWriteConsole 的多个顺序调用中,但最佳做法是在一次调用中包含整个序列。 有关实际如何使用这些序列的示例,请参阅本主题末尾的示例

下表描述了简单的转义序列,其中一个操作命令紧跟 ESC 字符之后。 这些序列没有参数,且立即生效。

此表中的所有命令通常都等效于调用 SetConsoleCursorPosition 控制台 API 来放置光标

光标移动将由当前视区绑定到缓冲区。 滚动(如果可用)不会发生。

序列 简写 行为
ESC M RI 反向索引 – 执行 \n 的反向操作,将游标上移一行,保持水平位置,如有必要,滚动缓冲区*
ESC 7 DECSC 将游标位置保存在内存中**
ESC 8 DECSR 从内存中还原游标位置**

注意

* 如果设置了滚动边距,则边距内的 RI 将仅滚动边距的内容,并保持视区不变。 (请参阅滚动边距)

**在首次使用 save 命令之前,内存中不会保存任何值。 访问保存值的唯一方法就是使用 restore 命令。

光标定位

下表包含控制序列引导 (CSI) 类型序列。 所有 CSI 序列都以 ESC (0x1B) 开头,后跟 [ (左方括号,0x5B) ,并且可能包含可变长度的参数,以指定每个操作的详细信息。 这将由速记 <n> 表示。 下面的每个表按功能分组,每个表下方都有注释,说明组的工作原理。

对于所有参数,除非另有说明,否则适用以下规则:

  • <n> 表示移动距离,是可选参数
  • 如果省略 <n> 或等于 0,则将其视为 1
  • <n> 不能大于 32,767(最大短值)
  • <n> 不能为负

本部分中的所有命令通常等效于调用 SetConsoleCursorPosition 控制台 API

光标移动将由当前视区绑定到缓冲区。 滚动(如果可用)不会发生。

序列 代码 说明 行为
ESC [ <n> A CUU 光标向上 光标向上 <n> 行
ESC [ <n> B CUD 光标向下 光标向下 <n> 行
ESC [ <n> C CUF 光标向前 光标向前(右)<n> 行
ESC [ <n> D CUB 光标向后 光标向后(左)<n> 行
ESC [ <n> E CNL 光标下一行 光标从当前位置向下 <n> 行
ESC [ <n> F CPL 光标当前行 光标从当前位置向上 <n> 行
ESC [ <n> G CHA 绝对光标水平 光标在当前行中水平移动到第 <n> 个位置
ESC [ <n> d VPA 绝对垂直行位置 光标在当前列中垂直移动到第 <n> 个位置
ESC [ <y> ; <x> H CUP 光标位置 游标移动到视区中的 <x>;<y> 坐标,其中 <x> 是 <y> 行的列
ESC [ <y> ; <x> f HVP 水平垂直位置 游标移动到视区中的 <x>;<y> 坐标,其中 <x> 是 <y> 行的列
ESC [ s ANSISYSSC 保存光标 - Ansi.sys 模拟 **在没有参数的情况下,执行保存游标操作,如 DECSC
ESC [ u ANSISYSRC 还原光标 - Ansi.sys 模拟 **在没有参数的情况下,执行还原游标操作,如 DECRC

注意

*<x> 和 <y> 参数的限制与上面的 <n> 相同。 如果省略 <x> 和 <y>,则将其设置为 1;1。

**ANSI.sys 历史文档可在 https://msdn.microsoft.com/library/cc722862.aspx 中找到,并且是为了方便/兼容性而实现的。

光标可见性

以下命令控制光标的可见性及其闪烁状态。 DECTCEM 序列通常等效于调用 SetConsoleCursorInfo 控制台 API 来切换光标可见性

序列 代码 说明 行为
ESC [ ? 12 小时 ATT160 文本光标启用闪烁 开始光标闪烁
ESC [ ? 12 l ATT160 文本光标禁用闪烁 停止闪烁光标
ESC [ ? 25 h DECTCEM 文本光标启用模式显示 显示光标
ESC [ ? 25 l DECTCEM 文本光标启用模式隐藏 隐藏光标

提示

启用序列以小写 H 字符 (h) 结束,禁用序列以小写 L 字符 (l) 结束。

游标形状

以下命令控制并允许自定义游标形状。

序列 代码 说明 行为
ESC [ 0 SP q DECSCUSR 用户形状 用户配置的默认游标形状
ESC [ 1 SP q DECSCUSR 闪烁块 闪烁块游标形状
ESC [ 2 SP q DECSCUSR 稳定块 稳定块游标形状
ESC [ 3 SP q DECSCUSR 闪烁下划线 闪烁下划线游标形状
ESC [ 4 SP q DECSCUSR 稳定下划线 稳定下划线游标形状
ESC [ 5 SP q DECSCUSR 闪烁条 闪烁条游标形状
ESC [ 6 SP q DECSCUSR 稳定条 稳定条游标形状

注意

SP 是中间位置的文本空格字符 (0x20),后跟最后位置的 q (0x71)。

视区定位

本部分中的所有命令通常等效于调用 ScrollConsoleScreenBuffer 控制台 API 来移动控制台缓冲区的内容

警告命令名称有误导性。 滚动是指文本在操作过程中移动的方向,而不是视区的移动方向。

序列 代码 说明 行为
ESC [ <n> S SU 向上滚动 将文本向上滚动 <n> 行。 也称为向下平移,新行从屏幕底部填充
ESC [ <n> T SD 向下滚动 向下滚动 <n> 行。 也称为向上平移,新行从屏幕顶部填充

文本从光标所在的行开始移动。 如果光标位于视区的中间行,则向上滚动将移动视区的下半部分,并在底部插入空白行。 向下滚动将移动视区行的上半部分,并在顶部插入新行。

请注意,上下滚动也受滚动边距的影响。 上下滚动不会影响滚动边距以外的任何行。

<n> 的默认值为 1,可以选择性地省略该值。

文本修改

本部分中的所有命令通常等效于调用 FillConsoleOutputCharacterFillConsoleOutputAttributeScrollConsoleScreenBuffer 控制台 API 来修改文本缓冲内容

序列 代码 说明 行为
ESC [ <n> @ ICH 插入字符 在当前光标位置插入 <n> 个空格,这会将所有现有文本移到右侧。 向右溢出屏幕的文本会被删除。
ESC [ <n> P DCH 删除字符 删除当前光标位置的 <n> 个字符,这会从屏幕右边缘以空格字符移动。
ESC [ <n> X ECH 擦除字符 擦除当前光标位置的 <n> 个字符,方法是使用空格字符覆盖它们。
ESC [ <n> L IL 插入行 将 <n> 行插入光标位置的缓冲区。 光标所在的行及其下方的行将向下移动。
ESC [ <n> M DL 删除行 从缓冲区中删除 <n> 行,从光标所在的行开始。

注意

对于 IL 和 DL,仅影响滚动边距中的行(请参阅滚动边距)。 如果未设置边距,则默认边距边框为当前视区。 如果行移到边距下方,则它们将被丢弃。 删除行时,空白行将插入边距的底部,视区外部的行永远不会受到影响。

对于每个序列,如果省略,则 <n> 的默认值为 0。

对于以下行,参数 <n> 有 3 个有效值:

  • 0 的擦除范围是从当前光标位置(含)到行/显示的末尾
  • 1 的擦除范围是从行/显示开始到当前光标位置(含)
  • 2 的擦除范围是整行/显示
序列 代码 说明 行为
ESC [ <n> J ED 显示中的擦除 将 <n> 指定的当前视区/屏幕中的所有文本替换为空格字符
ESC [ <n> K EL 行中的擦除 将行上的所有文本替换为由 <n> 指定的光标与空格字符

文本格式

本部分中的所有命令通常等效于调用 SetConsoleTextAttribute 控制台 API 来调整对控制台输出文本缓冲区的所有未来写入的格式

此命令很特别,因为下面的 <n> 位置可以接受 0 到 16 个用分号分隔的参数。

如果未指定任何参数,它会被视为单个 0 参数。

序列 代码 说明 行为
ESC [ <n> m SGR 设置图形呈现内容 将屏幕和文本的格式设置为由 <n> 指定

以下值表可用于 <n> 以表示不同的格式设置模式。

格式设置模式从左到右应用。 应用相互矛盾的格式设置选项将导致最右的选项优先。

对于指定颜色的选项,这些颜色的使用会遵循控制台颜色表中的定义,这可以使用 SetConsoleScreenBufferInfoEx API 进行修改。 如果通过修改表使表中的“蓝色”位置显示红色 RGB 阴影,则对“前景蓝色”的所有调用将显示该红色,除非另有更改

说明 行为
0 默认 将所有属性返回到修改前的默认状态
1 粗体/亮 将亮度/强度标志应用于前景色
22 无粗体/亮度 从前景色中删除亮度/强度标志
4 下划线 添加下划线
24 无下划线 删除下划线
7 消极 交换前景色和背景色
27 正(非负) 将前景/背景返回到正常
30 前景黑色 将非粗体/亮黑色应用于前景
31 前景红色 将非粗体/亮红色应用于前景
32 前景绿色 将非粗体/亮绿色应用于前景
33 前景黄色 将非粗体/亮黄色应用于前景
34 前景蓝色 将非粗体/亮蓝色应用于前景
35 前景品红 将非粗体/亮品红应用于前景
36 前景蓝绿色 将非粗体/亮蓝绿色应用于前景
37 前景白色 将非粗体/亮白色应用于前景
38 前景扩展色 将扩展色值应用于前景(请参阅下面的详细信息)
39 前景默认色 仅应用默认的前景部分(请参阅 0)
40 背景黑色 将非粗体/亮黑色应用于背景
41 背景红色 将非粗体/亮红色应用于背景
42 背景绿色 将非粗体/亮绿色应用于背景
43 背景黄色 将非粗体/亮黄色应用于背景
44 背景蓝色 将非粗体/亮蓝色应用于背景
45 背景品红 将非粗体/亮品红应用于背景
46 背景蓝绿色 将非粗体/亮蓝绿色应用于背景
47 背景白色 将非粗体/亮白色应用于背景
48 背景扩展色 将扩展色值应用于背景(请参阅下面的详细信息)
49 背景默认色 仅应用默认的背景部分(请参阅 0)
90 前景亮黑色 将粗体/亮黑色应用于前景
91 前景亮红色 将粗体/亮红色应用于前景
92 前景亮绿色 将粗体/亮绿色应用于前景
93 前景亮黄色 将粗体/亮黄色应用于前景
94 前景亮蓝色 将粗体/亮蓝色应用于前景
95 前景亮品红 将粗体/亮品红应用于前景
96 前景亮蓝绿色 将粗体/亮蓝绿色应用于前景
97 前景亮白色 将粗体/亮白色应用于前景
100 背景亮黑色 将粗体/亮黑色应用于背景
101 背景亮红色 将粗体/亮红色应用于背景
102 背景亮绿色 将粗体/亮绿色应用于背景
103 背景亮黄色 将粗体/亮黄色应用于背景
104 背景亮蓝色 将粗体/亮蓝色应用于背景
105 背景亮品红 将粗体/亮品红应用于背景
106 背景亮蓝绿色 将粗体/亮蓝绿色应用于背景
107 背景亮白色 将粗体/亮白色应用于背景

扩展颜色

某些虚拟终端仿真器支持比 Windows 控制台提供的 16 种颜色更多的颜色调色板。 对于这些扩展颜色,Windows 控制台将从现有的 16 色表中选择最接近的适当颜色进行显示。 与上述典型 SGR 值不同,扩展值将根据下表在初始指标之后使用其他参数。

SGR 子序列 说明
38 ; 2 ; <r> ; <g> ; <b> 将前景色设置为 <r>、<g>、<b> 参数中指定的 RGB 值*
48 ; 2 ; <r> ; <g> ; <b> 将背景色设置为 <r>、<g>、<b> 参数中指定的 RGB 值*
38 ; 5 ; <s> 将前景色设置为 88 或 256 颜色表中的 <s> 索引*
48 ; 5 ; <s> 将背景色设置为 88 或 256 颜色表中的 <s> 索引*

*内部维护的用于比较的 88 和 256 调色板基于 xterm 终端仿真器。 此时不能修改比较/舍入表。

屏幕颜色

以下命令允许应用程序将屏幕调色板值设置为任何 RGB 值。

RGB 值应为介于 0ff 之间的十六进制值,并用正斜杠字符(例如 rgb:1/24/86)分隔。

请注意,此序列是 OSC“操作系统命令”序列,而不是与列出的许多其他序列一样为 CSI,因此以“\x1b]”而不是“\x1b[”开头。 作为 OSC 序列,它们以表示为 <ST> 的字符串终止符结尾,并使用 ESC \ (0x1B 0x5C) 进行传输。 BEL (0x7) 也可用作终止符,但首选较长的形式。

Sequence 说明 行为
ESC ] 4 ; <i> ; rgb : <r> / <g> / <b><ST> 修改屏幕颜色 将屏幕颜色调色板索引 <i> 设置为在 <r>、<g>、<b> 中指定的 RGB 值

模式更改

这些是控制输入模式的序列。 有两组不同的输入模式:光标键模式和键盘键模式。 光标键模式控制箭头键以及“Home”和“End”发出的序列,而键盘键模式主要控制数字键盘上的键以及功能键发出的序列。

每种模式都是简单的布尔设置 - 光标键模式是常规(默认)或应用程序,键盘键模式是数字(默认)或应用程序。

有关在这些模式下发出的序列,请参阅光标键以及数字键盘和功能键部分。

序列 代码 说明 行为
ESC = DECKPAM 启用键盘应用程序模式 键盘键将发出其应用程序模式序列。
ESC > DECKPNM 启用键盘数字模式 键盘键将发出其数字模式序列。
ESC [ ? 1 小时 DECCKM 启用光标键应用程序模式 键盘键将发出其应用程序模式序列。
ESC [ ? 1 l DECCKM 禁用光标键应用程序模式(使用“常规”模式) 键盘键将发出其数字模式序列。

查询状态

本节中的所有命令通常等效于调用 Get* 控制台 API 来检索有关当前控制台缓冲区状态的状态信息。

注意

设置 ENABLE_VIRTUAL_TERMINAL_PROCESSING 时,这些查询在输出流上被识别后,会立即将其响应发送到控制台输入流。 ENABLE_VIRTUAL_TERMINAL_INPUT 标志不适用于查询命令,因为假定进行查询的应用程序始终希望接收答复。

序列 代码 说明 行为
ESC [ 6 n DECXCPR 报告光标位置 将游标位置发出为:ESC [ <r> ; <c> R,其中 <r> = 游标行,<c> = 游标列
ESC [ 0 c DA 设备属性 报告终端标识。 将发出“\x1b[?1;0c”,指示“VT101 无选项”。

制表符

虽然 Windows 控制台传统上需要制表符只有 8 个字符宽,但使用某些序列的 *nix 应用程序可以操纵制表位在控制台窗口中的位置,以优化应用程序的游标移动。

以下序列允许应用程序在控制台窗口中设置制表位的位置、删除它们并在它们之间导航。

序列 代码 说明 行为
ESC H HTS 水平制表符设置 在光标所在的当前列中设置制表位。
ESC [ <n> I CHT 光标水平(向前)制表符 使用制表位将光标前进到下一列(在同一行中)。 如果没有更多的制表位,请移动到行中的最后一列。 如果光标位于最后一列中,则移动到下一行的第一列。
ESC [ <n> Z CBT 光标向后制表符 使用制表位将光标移动到前一列(在同一行中)。 如果没有更多制表位,则将光标移到第一列。 如果光标位于第一列中,则不移动光标。
ESC [ 0 g TBC 制表符清除(当前列) 清除当前列中的制表位(如果有)。 否则不执行任何操作。
ESC [ 3 g TBC 制表符清除(所有列) 清除当前设置的所有制表位。
  • 对于 CHT 和 CBT,<n> 是一个可选参数(默认 = 1),指示向指定方向移动光标的次数。
  • 如果没有通过 HTS 设置制表位,CHT 和 CBT 会将窗口的第一列和最后一列视为唯一的两个制表位。
  • 使用 HTS 设置制表位还会导致控制台导航到 TAB (0x09输出上的下一个制表位,“\t”) 字符,方式与 CHT 相同。

指定字符集

以下序列允许程序更改活动字符集映射。 这允许程序发出 7 位 ASCII 字符,但将它们显示为终端屏幕上的其他字形。 目前,只有两个受支持的字符集:ASCII(默认)和 DEC 特殊图形字符集。 有关 DEC 特殊图形字符集表示的所有字符列表,请参阅 http://vt100.net/docs/vt220-rm/table2-4.html

Sequence 说明 行为
ESC ( 0 指定字符集 - DEC 线条绘制 启用 DEC 线条绘制模式
ESC ( B 指定字符集 - US ASCII 启用 ASCII 模式(默认值)

值得注意的是,DEC 线条绘制模式用于在控制台应用程序中绘制边框。 下表显示了 ASCII 字符与线条绘制字符之间的对应关系。

Hex ASCII DEC 线条绘制
0x6a j
0x6b k
0x6c l
0x6d m
0x6e n
0x71 q
0x74 t
0x75 u
0x76 v
0x77 w
0x78 x

滚动边距

以下序列允许程序配置受滚动操作影响的屏幕的“滚动区域”。 这是当屏幕滚动时调整的行的子集,例如,在“\n”或 RI 上滚动。 这些边距还会影响插入行 (IL) 和删除行 (DL)、向上滚动 (SU) 和向下滚动 (SD) 修改的行。

滚动边距对于以下情况特别有用:屏幕的一部分不滚动,而屏幕的其余部分已填充,例如在应用程序的顶部有一个标题栏或在应用程序的底部有一个状态栏。

对于 DECSTBM,有两个可选参数 <t> 和 <b>,用于指定表示滚动区域(含)的顶部和底部行。 如果省略参数,则 <t> 默认为 1,<b> 默认为当前视区高度。

滚动边距基于缓冲区,因此务必确保备用缓冲区和主缓冲区保持单独的滚动边距设置(因此备用缓冲区中的全屏应用程序不会影响主缓冲区的边距)。

序列 代码 说明 行为
ESC [ <t> ; <b> r DECSTBM 设置滚动区域 设置视区的 VT 滚动边距。

窗口标题

以下命令允许应用程序将控制台窗口的标题设置为给定的 <string> 参数。 字符串必须小于 255 个字符才能被接受。 这等效于使用给定字符串调用 SetConsoleTitle。

请注意,这些序列是 OSC“操作系统命令”序列,而不是列出的许多其他序列的 CSI,因此以“\x1b]”而不是“\x1b[”开头。 作为 OSC 序列,它们以表示为 <ST> 的字符串终止符结尾,并使用 ESC \ (0x1B 0x5C) 进行传输。 BEL (0x7) 也可用作终止符,但首选较长的形式。

Sequence 说明 行为
ESC ] 0 ; <string><ST> 设置窗口标题 将控制台窗口的标题设置为 <string>。
ESC ] 2 ; <string><ST> 设置窗口标题 将控制台窗口的标题设置为 <string>。

此处的终止字符是“Bell”字符“\x07”

备用屏幕缓冲区

*Nix 样式的应用程序通常使用备用屏幕缓冲区,以便它们可以修改缓冲区的整个内容,而不会影响启动它们的应用程序。 备用缓冲区刚好是窗口的尺寸,没有任何回滚区域。

此行为的一个示例是从 bash 启动 vim。 Vim 使用整个屏幕编辑文件,然后返回到 bash 时保留原始缓冲区不变。

Sequence 说明 行为
ESC [ ? 1 0 4 9 h 使用备用屏幕缓冲区 切换到新的备用屏幕缓冲区。
ESC [ ? 1 0 4 9 l 使用主屏幕缓冲区 切换到主缓冲区。

窗口宽度

以下序列可用于控制控制台窗口的宽度。 它们大致等效于调用 SetConsoleScreenBufferInfoEx 控制台 API 来设置窗口宽度。

序列 代码 说明 行为
ESC [ ? 3 h DECCOLM 将列数设置为 132 将控制台宽度设置为 132 列宽。
ESC [ ? 3 l DECCOLM 将列数设置为 80 将控制台宽度设置为 80 列宽。

软重置

以下序列可用于将某些属性重置为默认值。以下属性将重置为以下默认值(还列出了控制这些属性的序列):

  • 光标可见性:可见 (DECTEM)
  • 数字键盘:数字模式 (DECNKM)
  • 光标键模式:常规模式 (DECCKM)
  • 顶部边距和底部边距:顶部 = 1,底部 = 控制台高度 (DECSTBM)
  • 字符集:US ASCII
  • 图形呈现内容:默认/关闭 (SGR)
  • 保存游标状态:Home 位置 (0,0) (DECSC)
序列 代码 说明 行为
ESC [ ! p DECSTR 软重置 将某些终端设置重置为默认值。

输入序列

如果使用 SetConsoleMode 标志在输入缓冲区句柄上设置了 ENABLE_VIRTUAL_TERMINAL_INPUT 标志,则控制台主机在输入流上发出以下终端序列。

以下两种内部模式控制为给定输入键发出哪些序列:光标键模式和键盘键模式。 “模式更改”部分中介绍了这些内容。

光标键

密钥 常规模式 应用程序模式
向上键 ESC [ A ESC O A
向下键 ESC [ B ESC O B
向右键 ESC [ C ESC O C
向左键 ESC [ D ESC O D
主页 ESC [ H ESC O H
End ESC [ F ESC O F

此外,如果按 Ctrl 的同时使用这些键,则无论光标键模式如何,都会发出以下序列:

密钥 任何模式
Ctrl + 向上键 ESC [ 1 ; 5 A
Ctrl + 向下键 ESC [ 1 ; 5 B
Ctrl + 向右键 ESC [ 1 ; 5 C
Ctrl + 向左键 ESC [ 1 ; 5 D

数字键盘和功能键

密钥 序列
Backspace 0x7f (DEL)
暂停 0x1a (SUB)
Escape 0x1b (ESC)
Insert ESC [ 2 ~
删除 ESC [ 3 ~
Page Up ESC [ 5 ~
Page Down ESC [ 6 ~
F1 ESC O P
F2 ESC O Q
F3 ESC O R
F4 ESC O S
F5 ESC [ 1 5 ~
F6 ESC [ 1 7 ~
F7 ESC [ 1 8 ~
F8 ESC [ 1 9 ~
F9 ESC [ 2 0 ~
F10 ESC [ 2 1 ~
F11 ESC [ 2 3 ~
F12 ESC [ 2 4 ~

修改键

通过使用转义为序列添加前缀来处理 Alt:ESC <c>,其中 <c> 是操作系统传递的字符。 Alt+Ctrl 的处理方式相同,只是操作系统会将 <c> 键预移到将中继到应用程序的适当控制字符。

Ctrl 的传递方式通常与从系统接收完全相同。 这通常是向下移到控制字符保留空间 (0x0-0x1f) 的单个字符。 例如,Ctrl+@ (0x40) 变为 NUL (0x00),Ctrl+[ (0x5b) 变为 ESC (0x1b) 等。根据下表,将专门处理一些 Ctrl 组合键:

密钥 序列
Ctrl + 空格键 0x00 (NUL)
Ctrl + 向上键 ESC [ 1 ; 5 A
Ctrl + 向下键 ESC [ 1 ; 5 B
Ctrl + 向右键 ESC [ 1 ; 5 C
Ctrl + 向左键 ESC [ 1 ; 5 D

注意

Ctrl + 右 Alt 被视为 AltGr。 当两者同时出现时,它们将被去除,系统显示的字符的 Unicode 值将传递到目标中。 系统将根据当前系统输入设置预转换 AltGr 值。

示例

SGR 终端序列的示例

以下代码提供了几个文本格式的示例。

#include <stdio.h>
#include <wchar.h>
#include <windows.h>

int main()
{
    // Set output mode to handle virtual terminal sequences
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut == INVALID_HANDLE_VALUE)
    {
        return GetLastError();
    }

    DWORD dwMode = 0;
    if (!GetConsoleMode(hOut, &dwMode))
    {
        return GetLastError();
    }

    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hOut, dwMode))
    {
        return GetLastError();
    }

    // Try some Set Graphics Rendition (SGR) terminal escape sequences
    wprintf(L"\x1b[31mThis text has a red foreground using SGR.31.\r\n");
    wprintf(L"\x1b[1mThis text has a bright (bold) red foreground using SGR.1 to affect the previous color setting.\r\n");
    wprintf(L"\x1b[mThis text has returned to default colors using SGR.0 implicitly.\r\n");
    wprintf(L"\x1b[34;46mThis text shows the foreground and background change at the same time.\r\n");
    wprintf(L"\x1b[0mThis text has returned to default colors using SGR.0 explicitly.\r\n");
    wprintf(L"\x1b[31;32;33;34;35;36;101;102;103;104;105;106;107mThis text attempts to apply many colors in the same command. Note the colors are applied from left to right so only the right-most option of foreground cyan (SGR.36) and background bright white (SGR.107) is effective.\r\n");
    wprintf(L"\x1b[39mThis text has restored the foreground color only.\r\n");
    wprintf(L"\x1b[49mThis text has restored the background color only.\r\n");

    return 0;
}

注意

在前面的示例中,字符串“\x1b[31m”是 ESC [ <n> m 的实现,<n> 为 31。

下图显示了上一个代码示例的输出。

output of the console using the sgr command

启用虚拟终端处理的示例

以下代码提供了为应用程序启用虚拟终端处理的建议方法的示例。 该示例的目的是演示:

  1. 在设置 SetConsoleMode 之前,应始终通过 GetConsoleMode 检索现有模式并进行分析。

  2. 要确定何时在下级系统上运行,当前的机制是检查 SetConsoleMode 是否返回 0 以及 GetLastError 是否返回 ERROR_INVALID_PARAMETER。 在位字段中具有较新的控制台模式标志之一的接收 ERROR_INVALID_PARAMETER 的应用程序应正常进行行为降级,然后重试。

#include <stdio.h>
#include <wchar.h>
#include <windows.h>

int main()
{
    // Set output mode to handle virtual terminal sequences
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut == INVALID_HANDLE_VALUE)
    {
        return false;
    }
    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
    if (hIn == INVALID_HANDLE_VALUE)
    {
        return false;
    }

    DWORD dwOriginalOutMode = 0;
    DWORD dwOriginalInMode = 0;
    if (!GetConsoleMode(hOut, &dwOriginalOutMode))
    {
        return false;
    }
    if (!GetConsoleMode(hIn, &dwOriginalInMode))
    {
        return false;
    }

    DWORD dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
    DWORD dwRequestedInModes = ENABLE_VIRTUAL_TERMINAL_INPUT;

    DWORD dwOutMode = dwOriginalOutMode | dwRequestedOutModes;
    if (!SetConsoleMode(hOut, dwOutMode))
    {
        // we failed to set both modes, try to step down mode gracefully.
        dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
        dwOutMode = dwOriginalOutMode | dwRequestedOutModes;
        if (!SetConsoleMode(hOut, dwOutMode))
        {
            // Failed to set any VT mode, can't do anything here.
            return -1;
        }
    }

    DWORD dwInMode = dwOriginalInMode | dwRequestedInModes;
    if (!SetConsoleMode(hIn, dwInMode))
    {
        // Failed to set VT input mode, can't do anything here.
        return -1;
    }

    return 0;
}

精选周年更新功能的示例

下面的示例是使用各种转义序列来操纵缓冲区的代码的较可靠的示例,侧重于介绍 Windows 10 周年更新中添加的功能。

本示例使用备用屏幕缓冲区,从而操纵制表位、设置滚动边距并更改字符集。

// System headers
#include <windows.h>

// Standard library C-style
#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>

#define ESC "\x1b"
#define CSI "\x1b["

bool EnableVTMode()
{
    // Set output mode to handle virtual terminal sequences
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut == INVALID_HANDLE_VALUE)
    {
        return false;
    }

    DWORD dwMode = 0;
    if (!GetConsoleMode(hOut, &dwMode))
    {
        return false;
    }

    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (!SetConsoleMode(hOut, dwMode))
    {
        return false;
    }
    return true;
}

void PrintVerticalBorder()
{
    printf(ESC "(0"); // Enter Line drawing mode
    printf(CSI "104;93m"); // bright yellow on bright blue
    printf("x"); // in line drawing mode, \x78 -> \u2502 "Vertical Bar"
    printf(CSI "0m"); // restore color
    printf(ESC "(B"); // exit line drawing mode
}

void PrintHorizontalBorder(COORD const Size, bool fIsTop)
{
    printf(ESC "(0"); // Enter Line drawing mode
    printf(CSI "104;93m"); // Make the border bright yellow on bright blue
    printf(fIsTop ? "l" : "m"); // print left corner 

    for (int i = 1; i < Size.X - 1; i++)
        printf("q"); // in line drawing mode, \x71 -> \u2500 "HORIZONTAL SCAN LINE-5"

    printf(fIsTop ? "k" : "j"); // print right corner
    printf(CSI "0m");
    printf(ESC "(B"); // exit line drawing mode
}

void PrintStatusLine(const char* const pszMessage, COORD const Size)
{
    printf(CSI "%d;1H", Size.Y);
    printf(CSI "K"); // clear the line
    printf(pszMessage);
}

int __cdecl wmain(int argc, WCHAR* argv[])
{
    argc; // unused
    argv; // unused
    //First, enable VT mode
    bool fSuccess = EnableVTMode();
    if (!fSuccess)
    {
        printf("Unable to enter VT processing mode. Quitting.\n");
        return -1;
    }
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut == INVALID_HANDLE_VALUE)
    {
        printf("Couldn't get the console handle. Quitting.\n");
        return -1;
    }

    CONSOLE_SCREEN_BUFFER_INFO ScreenBufferInfo;
    GetConsoleScreenBufferInfo(hOut, &ScreenBufferInfo);
    COORD Size;
    Size.X = ScreenBufferInfo.srWindow.Right - ScreenBufferInfo.srWindow.Left + 1;
    Size.Y = ScreenBufferInfo.srWindow.Bottom - ScreenBufferInfo.srWindow.Top + 1;

    // Enter the alternate buffer
    printf(CSI "?1049h");

    // Clear screen, tab stops, set, stop at columns 16, 32
    printf(CSI "1;1H");
    printf(CSI "2J"); // Clear screen

    int iNumTabStops = 4; // (0, 20, 40, width)
    printf(CSI "3g"); // clear all tab stops
    printf(CSI "1;20H"); // Move to column 20
    printf(ESC "H"); // set a tab stop

    printf(CSI "1;40H"); // Move to column 40
    printf(ESC "H"); // set a tab stop

    // Set scrolling margins to 3, h-2
    printf(CSI "3;%dr", Size.Y - 2);
    int iNumLines = Size.Y - 4;

    printf(CSI "1;1H");
    printf(CSI "102;30m");
    printf("Windows 10 Anniversary Update - VT Example");
    printf(CSI "0m");

    // Print a top border - Yellow
    printf(CSI "2;1H");
    PrintHorizontalBorder(Size, true);

    // // Print a bottom border
    printf(CSI "%d;1H", Size.Y - 1);
    PrintHorizontalBorder(Size, false);

    wchar_t wch;

    // draw columns
    printf(CSI "3;1H");
    int line = 0;
    for (line = 0; line < iNumLines * iNumTabStops; line++)
    {
        PrintVerticalBorder();
        if (line + 1 != iNumLines * iNumTabStops) // don't advance to next line if this is the last line
            printf("\t"); // advance to next tab stop

    }

    PrintStatusLine("Press any key to see text printed between tab stops.", Size);
    wch = _getwch();

    // Fill columns with output
    printf(CSI "3;1H");
    for (line = 0; line < iNumLines; line++)
    {
        int tab = 0;
        for (tab = 0; tab < iNumTabStops - 1; tab++)
        {
            PrintVerticalBorder();
            printf("line=%d", line);
            printf("\t"); // advance to next tab stop
        }
        PrintVerticalBorder();// print border at right side
        if (line + 1 != iNumLines)
            printf("\t"); // advance to next tab stop, (on the next line)
    }

    PrintStatusLine("Press any key to demonstrate scroll margins", Size);
    wch = _getwch();

    printf(CSI "3;1H");
    for (line = 0; line < iNumLines * 2; line++)
    {
        printf(CSI "K"); // clear the line
        int tab = 0;
        for (tab = 0; tab < iNumTabStops - 1; tab++)
        {
            PrintVerticalBorder();
            printf("line=%d", line);
            printf("\t"); // advance to next tab stop
        }
        PrintVerticalBorder(); // print border at right side
        if (line + 1 != iNumLines * 2)
        {
            printf("\n"); //Advance to next line. If we're at the bottom of the margins, the text will scroll.
            printf("\r"); //return to first col in buffer
        }
    }

    PrintStatusLine("Press any key to exit", Size);
    wch = _getwch();

    // Exit the alternate buffer
    printf(CSI "?1049l");

}