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