ueber-devel / ueberzug

Continuation of ueberzug
GNU General Public License v3.0
92 stars 6 forks source link

Sixel backend support #7

Open eylles opened 1 year ago

eylles commented 1 year ago

thinking about this going forward and something i would like to have in 18.3.0 i have a couple ideas of how the sixel support should work which currently differ from how it works on ueberzugpp but looking at the current issues on ueberzugpp i think the best way is for both projects to have a compatible interface that aims at eliminating the problems.

the suggestions are:

what the hell do i mean with that, in short: ueberzug (and compatibles) should be able to output sixel ny itself whenever possible, from a developer writing an application that uses ueberzug to display images there should not be a large difference between outputting with x11 or sixel, all functions should be the same, however the output type should not be obscured and the output type must be explicit, as such the application using ueberzug for displaying images should have an easy way to know how is ueberzu displayin the images.

i do think that ueberzugpp is doing something right by utilizing a flag to force output sixel, but when no flag is passed ueberzug should try to detect if the terminal can output sixel, if it can then output with sixel.

so the suggerences i have put more concrete are as follow:

have an interface to force the usage of sixel, the output flag is okay but perhaps adding a check for an env var would not be a bad idea.

have an interface for ueberzug to tell the application which output method is used, to keep things easy and unix-like i think of setting an env var, something like UEBERZUG_OUTPUT_LAYER which could be either x11 or sixel, finally perhaps the hardest part, when no output method is forced ueberzug should check if it can output sixel, conveniently there is a standar escape sequence implemented by most sixel capable terminals and here's an example of it being used.

this comes from lsix

    # IS TERMINAL SIXEL CAPABLE?        # Send Device Attributes
    IFS=";" read -a REPLY -s -t 1 -d "c" -p $'\e[c' >&2
    for code in "${REPLY[@]}"; do
    if [[ $code == "4" ]]; then
        hassixel=yup
        break
    fi
    done

tho as noted not all sixel capable terminals implement that sequence, as such additional checks are left explicitly to the application programmer to do, at least for the time being.

cc @jstkdng please feel free to provide feedback on this proposal.

jstkdng commented 1 year ago

Testing if a terminal can display sixel's is kinda hard. The standard way is to send escape codes and read the terminal response.

The problem with sending escape codes is that the terminal doesn't return the answer to stdout, it sort of emulates keyboard input and through that keyboard input you get the output. e.g.

$ echo "\e[c"

$ 6c

That is a problem because there's another program that has to send commands to ueberzug, and said program might interpret the terminal response from the escape code as input from the user, breaking UX.

U++ has a way to find if the terminal supports sixel:

void Terminal::check_sixel_support()
{
    // some terminals support sixel but don't respond to escape sequences
    auto supported_terms = std::unordered_set<std::string_view> {
        "yaft-256color"
    };
    auto resp = read_raw_str("\e[?1;1;0S").erase(0, 3);
    auto vals = util::str_split(resp, ";");
    if (vals.size() > 2 || supported_terms.contains(term)) {
        flags.output = "sixel";
    }
}

but I have to hide that functionality behind a flag due to the break in UX.

    if (flags.use_escape_codes) {
        init_termios();
        if (xpixel == 0 && ypixel == 0) get_terminal_size_escape_code();
        if (flags.output.empty()) {
            check_sixel_support();
            check_kitty_support();
        }
        reset_termios();
    }

Because of this, I have implemented another way to send commands to U++ and that is through a unix socket. It fixes the UX issue but breaks support with OG ueberzug. I'm planning to open a PR on the ranger repo to support this and have also been proposing other projects to adopt this breaking change but so far none have done it.

eylles commented 1 year ago

ah, well, perhaps the best way after all is to have the program detect sixel support, and either use the sixel output flag or pass an env var to ueberzug, that way the change necessary to support sixel on a per program basis would not go beyond 10 lines of code at worst.

As a "courtesy" to most projects we could add a 'guide' to support sixel with the necessary snippets to check for sixel support and set the flag or env var in a handful of languages.

Above snippet already takes care of bash, we could add python, c/c++ and rust snippets since those seem to be the languages that usually interact with ueberzug.

jstkdng commented 1 year ago

Yeah that also works, apart from that it'd also be required to have the other projects connect their stdout to ueberzug's stdout, otherwise sixel won't work.