Skip to main content
ansicode

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[\x1b[?<Pm>s (save) \x1b[?<Pm>r (restore)
\\033[\033[?1000s / \033[?1000r
\\e[\e[?1000s / \e[?1000r
ESC [ESC [ ? Pm s / ESC [ ? Pm r
hex1b 5b 3f ... 73 / 1b 5b 3f ... 72

Description

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

PmOne 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

bash
# 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 modes
python
import 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
go
// 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()
javascript
// 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));
c
/* 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

Related sequences