lemnos / theme.sh

A script which lets you set your $terminal theme.
Other
912 stars 45 forks source link

Save and restore current theme #23

Closed timkgh closed 2 years ago

timkgh commented 2 years ago

Is there a way to save the current theme from the terminal to a file, if set by a terminal profile, not by theme.sh?

I'd like to be able to save the current custom gnome-terminal theme, call theme.sh with some built-in theme, ssh to a remote host, restore original theme when the ssh session is done.

lemnos commented 2 years ago

Not at the moment I'm afraid. If the current theme was set using theme.sh you can just save it via theme.sh -l|tail -n1 before setting the ssh theme and restoring it when ssh terminates.

If you want to be able to store terminal colors which haven't been set by theme.sh then you will need the ability to query and read terminal state. This seems to be possible on some terms but support is even more variable than the already fraught OSC4/11 codes used for theme setting. I might take a crack at implementing this tomorrow, but even if I succeed, support is likely to vary by terminal.

References:

https://github.com/microsoft/terminal/issues/3718

lemnos commented 2 years ago

Spent some time on this, I have a prototype working but it is quite fragile and fails badly on certain terms. Alacritty notably implements query sequences for background and foreground colors but doesn't implement them for the other color codes as evidenced here:

https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/ansi.rs#L972

Interestingly alacritty does implement sequence 104 which can be used to reset all terminal colors, but this is an even more obscure sequence and doesn't allow you to save the current state.

iTerm is missing the cursor color query escape and I'm not sure if it was ever resolved on windows term.

Additionally there isn't a good POSIX compliant way to read /dev/tty from a shell. Awk comes close to offering a solution but one of the main implementations (mawk) performs input buffering rendering it useless for the task. My current solution uses head -c which isn't technically POSIX compliant but is reasonably portable (macos, busybox, freebsd, coreutils). An alternative might be to use dd iteratively but that would be quite slow. Alternatively I could just use perl :P. All of this is a moot point if there isn't widespread support for query sequences anyway.

Given this, I am on the fence about whether or not to integrate my partial solution. Terminals which don't support the relevant query sequences may not fail gracefully. Having said that, a fair number of terminals do seem to support the relevant sequences (libvte terms/kitty/iterm (mostly)/stock macos term). But if the user already knows what the term is, they presumably have enough control over the environment to use theme.sh to set their theme in the first place.

timkgh commented 2 years ago

Thank you for the detailed explanation. It does look like a mess to make it work across terminals. For my personal use case I'd be OK with a one time dump of the colors from gnome-terminal and making a "default" theme that I can then set with theme.sh Do you happen to have an example script how I can do that in a format that theme.sh can then use? It doesn't have to be portable, it's fine if it only works on Ubuntu and gnome-terminal :)

lemnos commented 2 years ago

The following should dump the current theme in the theme.sh format if you terminal supports all the escape sequences. If it does not, this will hang and leave the terminal in an unusable state. It works on kitty and gnome-terminal but causes the likes of alacritty and st to hang by virtue of not supporting the requisite sequences.

I would recommend against relying on this and instead favour setting your theme in ~/.bashrc using theme.sh "$(theme.sh -l|tail -n1)" and using theme.sh -l|tail -n1 to obtain the current theme.

parse_response_sequences() {
awk '
    function parsecol(s) {
        match(s, /rgb:/)
        r=substr(s, RSTART+4, 2)
        g=substr(s, RSTART+9, 2)
        b=substr(s, RSTART+14, 2)
        return tolower("#"r g b)
    }

    /^11/{print "background:", parsecol($0)}
    /^12/{print "cursor:", parsecol($0)}
    /^10/{print "foreground:", parsecol($0)}
    /^4;/{split($0, a, ";");print a[2]": "parsecol($0)}
'
}

stty raw -echo

printf '\033]4;0;?\007\033]4;1;?\007\033]4;2;?\007\033]4;3;?\007\033]4;4;?\007\033]4;5;?\007\033]4;6;?\007\033]4;7;?\007\033]4;8;?\007\033]4;9;?\007\033]4;10;?\007\033]4;11;?\007\033]4;12;?\007\033]4;13;?\007\033]4;14;?\007\033]4;15;?\007\033]10;?\007\033]11;?\007\033]12;?\007\033]12;?\007' > /dev/tty

# NOTE: This will hang if the terminal does not respond to all escape sequences...
# requires gawk or any awk that is *not* mawk (which does input buffering).
resp=$(gawk -vRS='[\033\007]' 'NR%2==0{
    print substr($0, 2)
    if(++n == 19) exit
}' < /dev/tty)

stty -raw echo

echo "$resp"|parse_response_sequences
lemnos commented 2 years ago

I was able to make the solution a little more robust and integrate it into the script. You can now use -p to print the current terminal theme. This still won't work on terminals which don't support it but it should work on gnome-terminal. You can also pipe theme text directly into theme.sh now, so you can do something like the following

./theme.sh -p > /tmp/old_theme # Save old theme.
./theme.sh zenburn
...
./theme.sh < /tmp/old_theme # Restore old theme

Though I would still recommend against this in the use of scripts since it breaks on many common terms.

timkgh commented 2 years ago

Thank you for the super quick turnaround on this. Tried it on Ubuntu + gnome-terminal and it seems to work well.