XTSAVE / XTRESTORE — Save / restore DEC private mode (`CSI ? Pm s` / `CSI ? Pm r`)
Stash one or more DEC private mode states on a stack, then restore them later — the foundation tmux / screen / fzf use to safely toggle mouse / alt-screen / paste modes without trampling user prefs.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1b[?<Pm>s (save) \x1b[?<Pm>r (restore)\033[?1000s / \033[?1000r\e[?1000s / \e[?1000rESC [ ? Pm s / ESC [ ? Pm r1b 5b 3f ... 73 / 1b 5b 3f ... 72Description
The `?` prefix puts `s` and `r` into the **DEC private mode** namespace (without the prefix, `CSI Ps ; Ps s` is DECSLRM — see `decslrm` — and `CSI Ps ; Ps r` is DECSTBM — see `decstbm`). With the prefix, `Pm` is one or more semicolon-separated DEC private mode numbers (the same numbers you'd use with `CSI ? Pm h/l` to set/reset them). - **XTSAVE** (`\x1b[?<Pm>s`) — push the *current* state of each named mode onto a per-mode stack inside the terminal. Stack depth is per-mode and typically 1-deep (a second XTSAVE overwrites the first); xterm and a few others maintain a true stack of arbitrary depth. - **XTRESTORE** (`\x1b[?<Pm>r`) — pop and apply each named mode's saved state. If the mode was never saved, XTRESTORE is a no-op for that mode. - Multiple modes per call: `\x1b[?1000;1002;1006;1049s` saves the four mouse-and-altscreen modes in one round-trip; the symmetric `\x1b[?1000;1002;1006;1049r` restores them. **Why this exists** — interactive tools like tmux, GNU screen, fzf, atuin, neovim's `:terminal`, and lazygit need to temporarily change mouse-tracking, alt-screen, bracketed-paste, focus-event, etc. without leaving the user with broken settings if they crash or the user backgrounds them. The XTSAVE / XTRESTORE pair is the *non-destructive* way to do this — capture the user's preference, swap to the tool's preference, and roll back on exit regardless of what the user originally had. **Common save / restore set** (the 'tmux mouse set'): `?1000` (X10 mouse), `?1002` (cell motion mouse), `?1003` (all-motion mouse), `?1006` (SGR mouse encoding), `?1015` (urxvt mouse encoding), `?1049` (alt-screen with cursor save). Bracketed paste adds `?2004`; focus events `?1004`. **Relationship to `?1049`** — `\x1b[?1049h/l` is itself a *compound* sequence that effectively combines `?1047` (alt-screen) + `?1048` (cursor save) + an implicit save-on-enter / restore-on-exit of the alt-screen contents. Everywhere else, the explicit XTSAVE / XTRESTORE pair is what you should reach for. Don't double-wrap `?1049` in XTSAVE / XTRESTORE — it already handles the round-trip itself. **Caveats** — xterm / iTerm2 / Kitty / WezTerm / Ghostty / Alacritty / Konsole / Windows Terminal all implement; Linux console implements partially (only the most common modes); cmd.exe doesn't. Stack depth varies — assume 1 and don't nest saves. macOS Terminal's implementation is the spottiest; test against it specifically if you target it.
Spec citation: xterm-ctlseqs (XTSAVE / XTRESTORE — CSI ? Pm s / r)
Parameters
| Pm | One or more semicolon-separated DEC private mode numbers (e.g. `1000;1002;1006;1049`). Same numbers used with `CSI ? Pm h` (set) and `CSI ? Pm l` (reset). |
Examples
# Wrap an interactive subshell so mouse + alt-screen prefs survive it.\nprintf '\033[?1000;1002;1006;1049s' # save\nprintf '\033[?1000l\033[?1049h' # enter alt-screen, mouse off\nbash --noprofile --norc\nprintf '\033[?1049l' # leave alt-screen\nprintf '\033[?1000;1002;1006;1049r' # restore user's modesimport sys\nMODES = '1000;1002;1006;1049'\nsys.stdout.write(f'\x1b[?{MODES}s') # save\ntry:\n sys.stdout.write('\x1b[?1000h') # enable mouse for our app\n run_app()\nfinally:\n sys.stdout.write(f'\x1b[?{MODES}r') # restore// Defer-based save/restore wrapper for a TUI's mouse + altscreen setup.\nmodes := \"1000;1002;1006;1049\"\nfmt.Printf(\"\\x1b[?%ss\", modes)\ndefer fmt.Printf(\"\\x1b[?%sr\", modes)\nfmt.Print(\"\\x1b[?1049h\\x1b[?1000;1002;1006h\") // enter alt-screen + cell-motion + SGR\nrunTUI()// Node TUI — save on SIGINT/exit so user's mouse prefs always come back.\nconst modes = '1000;1002;1006;1049';\nprocess.stdout.write(`\\x1b[?${modes}s`);\nprocess.on('exit', () => process.stdout.write(`\\x1b[?${modes}r`));\nprocess.on('SIGINT', () => process.exit(130));/* Save -> enter alt-screen + mouse -> restore on signal. */\nstatic const char *MODES = \"1000;1002;1006;1049\";\nprintf(\"\\x1b[?%ss\", MODES);\nsignal(SIGINT, on_exit_restore);\nprintf(\"\\x1b[?1049h\\x1b[?1000;1002;1006h\");\nrun_tui();\nprintf(\"\\x1b[?%sr\", MODES);Terminal support
- xterm
- yes
- Linux console (fbcon)
- partial
- macOS Terminal.app
- partial
- iTerm2
- yes
- Windows Terminal
- yes
- cmd.exe / ConPTY
- no
- kitty
- yes
- alacritty
- yes
- WezTerm
- yes
- Ghostty
- yes
- GNOME Terminal
- yes
- 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 | partial | partial | yes | yes | no | yes | yes | yes | yes | yes | yes | no | no |