DECSCUSR query via DECRQSS — Read the current cursor shape (`\x1bP$q q\x1b\\`)
Round-trip the cursor-shape parameter — query side of DECSCUSR via DECRQSS, terminal replies with the current `Ps SP q` setting.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1bP$q q\x1b\\\033P$q q\033\\\eP$q q\e\\ESC P $ q SP q ESC \1b 50 24 71 20 71 1b 5cDescription
DECSCUSR (`dec-cursor-shape`) defines a *setter* — `\x1b[<Ps> SP q` to change cursor shape. There is no symmetrical setter-query (`?`-style) opcode the way OSC 10 / 11 / 12 supports; instead, the **query side** is layered on top of DECRQSS (`dcs-decrqss`): request the *settable* function whose final byte is `SP q` and let the terminal reply with its current state. **Request** — `\x1bP$q q\x1b\\`. Parse as: DCS frame (`\x1bP`), intermediate `$` + final `q` (DECRQSS opcode), payload `" q"` (the bytes that uniquely identify the DECSCUSR setter — its `SP` intermediate plus its `q` final), terminated by ST (`\x1b\\`). **Reply** — `\x1bP1$r<Ps> q\x1b\\` on success, where `<Ps>` is the current parameter (`0` user-default through `6` steady bar — see `dec-cursor-shape` for the full table). The leading `1` is DECRQSS's success indicator (`0` means 'invalid request'); the `$r` is its reply-frame intermediate+final. **Why this matters** — TUIs that save-and-restore the cursor shape around modal pop-ups (fuzzy finders, picker overlays in tmux / fzf / atuin / neovim) need the *original* shape to restore. Without this query, the only options are blindly resetting to `0` (user default, but that discards a parent app's override) or hard-coding a guess. The DECRQSS round-trip lets the popup capture and faithfully replay. **Example handshake** — bash with timeout: `printf '\x1bP$q q\x1b\\' > /dev/tty; IFS= read -r -d '\\' -t 0.1 reply < /dev/tty; echo "$reply" | grep -oE 'P1\\$r[0-9]+'` returns `P1$r6` if the cursor is a steady bar. **Caveats** — xterm / iTerm2 / Kitty / WezTerm / Ghostty / Alacritty / Konsole reply; Linux console / cmd.exe / macOS Terminal do not. Always implement a ~100 ms timeout and fall back to `Ps = 0` (user default) when no reply arrives. Some emulators reply `\x1bP0$r\x1b\\` (DECRQSS 'invalid') if cursor shape was never explicitly set — treat that as 'use default'.
Spec citation: xterm-ctlseqs (DECRQSS + DECSCUSR)
Parameters
| payload | The bytes identifying the setter being queried — here, `" q"` (SP + q), the intermediate + final of DECSCUSR. |
| reply Ps | Current shape: 0 = user default, 1 = blink block, 2 = steady block, 3 = blink underline, 4 = steady underline, 5 = blink bar, 6 = steady bar. |
Examples
# Capture current cursor shape (raw + timeout).\nold=$(stty -g); stty -echo raw min 0 time 1\nprintf '\033P$q q\033\\\\' > /dev/tty\nIFS= read -r -d '\\\\' reply < /dev/tty\nstty "$old"\nshape=$(echo "$reply" | sed -nE 's/.*P1\\$r([0-9]+).*/\\1/p')\necho "current cursor: ${shape:-default}"import sys, termios, tty, re\ndef cursor_shape():\n fd = sys.stdin.fileno(); old = termios.tcgetattr(fd); tty.setraw(fd)\n try:\n sys.stdout.write('\x1bP$q q\x1b\\\\'); sys.stdout.flush()\n buf = b''\n while b'\\x1b\\\\' not in buf:\n buf += sys.stdin.buffer.read1(64)\n m = re.search(rb'P1\\$r(\\d+)', buf)\n return int(m.group(1)) if m else 0\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old)// Save shape -> open popup with bar cursor -> restore.\nshape := queryCursorShape() // via DECRQSS round-trip\nfmt.Print(\"\\x1b[6 q\") // bar for popup\ndefer fmt.Printf(\"\\x1b[%d q\", shape) // restore on exit// Query, then echo a description.\nprocess.stdin.setRawMode(true); process.stdin.resume();\nprocess.stdout.write('\\x1bP$q q\\x1b\\\\');\nlet buf = '';\nprocess.stdin.on('data', chunk => {\n buf += chunk.toString();\n if (!buf.includes('\\x1b\\\\')) return;\n process.stdin.setRawMode(false); process.stdin.pause();\n const m = buf.match(/P1\\$r(\\d+)/);\n const names = ['default','blink block','steady block','blink underline','steady underline','blink bar','steady bar'];\n console.log(names[m ? +m[1] : 0]);\n});/* Round-trip the cursor shape — minimal raw read. */\nprintf(\"\\x1bP$q q\\x1b\\\\\"); fflush(stdout);\nchar buf[64]; int n = read(0, buf, sizeof buf - 1);\nint shape = 0;\nif (n > 0) { buf[n] = 0; sscanf(strstr(buf, \"P1$r\"), \"P1$r%d\", &shape); }Terminal support
- xterm
- yes
- Linux console (fbcon)
- no
- macOS Terminal.app
- no
- iTerm2
- yes
- Windows Terminal
- partial
- cmd.exe / ConPTY
- no
- kitty
- yes
- alacritty
- yes
- WezTerm
- yes
- Ghostty
- yes
- GNOME Terminal
- partial
- Konsole
- yes
- tmux
- no
- GNU screen
- no
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| yes | no | no | yes | partial | no | yes | yes | yes | yes | partial | yes | no | no |