跳到主要内容
ansicode

OSC 10 / 11 / 12 查询 — 探测默认前景 / 背景 / 光标色(深色对浅色)

向终端查询当前默认前景(10)/ 背景(11)/ 光标(12)色 —— 自动选择深色或浅色主题的标准做法。

字节形式

涵盖所有常见的字符串字面量写法,方便正反查找。

\\x1b[\x1b]10;?\x07 (fg) \x1b]11;?\x07 (bg) \x1b]12;?\x07 (cursor)
\\033[\033]10;?\007 / \033]11;?\007 / \033]12;?\007
\\e[\e]10;?\a / \e]11;?\a / \e]12;?\a
ESC [ESC ] 10 ; ? BEL / ESC ] 11 ; ? BEL / ESC ] 12 ; ? BEL
hex1b 5d 31 30 3b 3f 07 / 1b 5d 31 31 3b 3f 07 / 1b 5d 31 32 3b 3f 07

说明

OSC 颜色的*查询*形式 —— 把 OSC 10 / 11 / 12(分别参见 `osc-set-fg-bg` 与 `osc-cursor-color`)的颜色值改为 `?`,终端会在标准输入回复同一 OSC opcode 与一个 XParseColor 形状的颜色:`\x1b]<Ps>;rgb:RRRR/GGGG/BBBB\x07`(每通道 16 位的形式是规范回复,即使终端内部存 8 位也会重复扩展:`#1e1e2e` → `rgb:1e1e/1e1e/2e2e`)。终止符与发送时一致 —— 送 `BEL`(0x07)则回 `BEL`,送 `ST`(`\x1b\\`)则回 `ST`。 **深色 / 浅色自动检测** —— 这是 vim 的 `'background'` 自动检测、neovim 的 `bg=` 探测、[atuin](https://github.com/atuinsh/atuin) 的主题选择、`bat --color=auto` 等工具用来在用户未设 `COLORFGBG` 时选择配色方案的标准握手。算法: 1. 保存终端模式,切到 raw(无回显、无 ICANON、VMIN=0、VTIME=1)。 2. 发送 `\x1b]11;?\x07`。 3. 在标准输入读到 `BEL`(或 `ST`),解析三段 16 位十六进制。 4. 计算感知亮度 —— `Y = 0.299·R + 0.587·G + 0.114·B`(Rec. 601)—— 与 `0.5 · 0xFFFF` 比较。低于 ⇒ 深色主题,高于 ⇒ 浅色。一些实现用 WCAG 的 `(R+R+B+G+G+G) / 6` 快捷式提速。 5. 恢复终端模式。若约 100 ms 内无回复,回退到 `$COLORFGBG` / `$TERM_PROGRAM` 启发式。 **为何不直接看 `$COLORFGBG`** —— 只有 rxvt 与少数衍生终端会设;iTerm2 / Ghostty / WezTerm / Kitty / Windows Terminal 都不设。OSC 11 查询才是可移植答案。 **注意 —— OSC 12 光标查询**在三者中实现最不齐:xterm / iTerm2 / Kitty / WezTerm / Ghostty 正确回复;Linux console / cmd.exe 忽略;macOS Terminal 静默吞掉;老版 gnome-terminal 因长期存在的解析器 bug 返回的是*前景*色。把光标色回复当作尽力而为,缺失时回退到前景色。

规范出处: xterm-ctlseqs (OSC 10 / 11 / 12 query)

参数

Ps颜色槽位:10 = 默认前景、11 = 默认背景、12 = 文本光标颜色。
?查询占位符 —— 替换颜色值,请求当前设置而非修改它。

示例

bash
# Probe background brightness, exit 0 = dark, 1 = light.\nold=$(stty -g); stty -echo raw min 0 time 1\nprintf '\033]11;?\007' > /dev/tty\nIFS= read -r -d $'\\a' reply < /dev/tty\nstty "$old"\nhex=${reply##*rgb:}\nr=$((16#${hex%%/*})); g=${hex#*/}; g=$((16#${g%%/*})); b=$((16#${hex##*/}))\ny=$(( (299*r + 587*g + 114*b) / 1000 ))\n[ "$y" -lt 32767 ] && echo dark || echo light
python
import sys, termios, tty, select, re\nfd = sys.stdin.fileno(); old = termios.tcgetattr(fd); tty.setraw(fd)\ntry:\n    sys.stdout.write('\x1b]11;?\x07'); sys.stdout.flush()\n    r, _, _ = select.select([fd], [], [], 0.2)\n    if not r:\n        print('no reply — falling back'); sys.exit(2)\n    buf = b''\n    while not buf.endswith(b'\\x07'):\n        buf += sys.stdin.buffer.read1(64)\n    m = re.search(rb'rgb:([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)', buf, re.I)\n    R, G, B = (int(x, 16) for x in m.groups())\n    Y = (299*R + 587*G + 114*B) // 1000\n    print('dark' if Y < 32767 else 'light')\nfinally:\n    termios.tcsetattr(fd, termios.TCSADRAIN, old)
go
// Detect dark/light by querying OSC 11 (background).\nimport (\n    \"bufio\"; \"fmt\"; \"os\"; \"regexp\"; \"strconv\"\n    \"golang.org/x/term\"\n)\nfunc isDark() bool {\n    fd := int(os.Stdin.Fd())\n    st, _ := term.MakeRaw(fd); defer term.Restore(fd, st)\n    fmt.Print(\"\\x1b]11;?\\x07\")\n    rd := bufio.NewReader(os.Stdin)\n    line, _ := rd.ReadString(0x07)\n    m := regexp.MustCompile(`rgb:([0-9a-fA-F]+)/([0-9a-fA-F]+)/([0-9a-fA-F]+)`).FindStringSubmatch(line)\n    r, _ := strconv.ParseInt(m[1], 16, 32)\n    g, _ := strconv.ParseInt(m[2], 16, 32)\n    b, _ := strconv.ParseInt(m[3], 16, 32)\n    return (299*r + 587*g + 114*b)/1000 < 32767\n}
javascript
// Node — assumes raw stdin and a TTY.\nprocess.stdin.setRawMode(true); process.stdin.resume();\nprocess.stdout.write('\\x1b]11;?\\x07');\nlet buf = '';\nprocess.stdin.on('data', chunk => {\n    buf += chunk.toString();\n    if (!buf.endsWith('\\x07')) return;\n    process.stdin.setRawMode(false); process.stdin.pause();\n    const [, r, g, b] = buf.match(/rgb:([0-9a-f]+)\\/([0-9a-f]+)\\/([0-9a-f]+)/i);\n    const Y = (299 * parseInt(r, 16) + 587 * parseInt(g, 16) + 114 * parseInt(b, 16)) / 1000;\n    console.log(Y < 0x7fff ? 'dark' : 'light');\n});
c
/* Minimal OSC 11 query — error handling omitted. */\n#include <stdio.h>\n#include <termios.h>\n#include <unistd.h>\nint main(void) {\n    struct termios old, raw; tcgetattr(0, &old); raw = old;\n    raw.c_lflag &= ~(ICANON|ECHO); raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 2;\n    tcsetattr(0, TCSANOW, &raw);\n    printf(\"\\x1b]11;?\\x07\"); fflush(stdout);\n    char buf[128]; int n = read(0, buf, sizeof buf - 1);\n    tcsetattr(0, TCSANOW, &old);\n    if (n > 0) { buf[n] = 0; printf(\"reply: %s\\n\", buf); }\n    return 0;\n}

终端支持

xterm
支持
Linux console (fbcon)
不支持
macOS Terminal.app
部分
iTerm2
支持
Windows Terminal
支持
cmd.exe / ConPTY
不支持
kitty
支持
alacritty
支持
WezTerm
支持
Ghostty
支持
GNOME Terminal
部分
Konsole
支持
tmux
不支持
GNU screen
不支持

相关序列