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
yes
GNU screen
yes

Related sequences