eylles / vifm-sixel-preview

vifm image previews for sixel capable terminals
24 stars 1 forks source link

Improvs #2

Closed eylles closed 2 years ago

eylles commented 2 years ago

this is a branch i was working on trying to add stuff from lsix to have detection of terminal size in cols and pixels, from looking at how it gets the terminal properties.

the change to bash is since i have not found any way to properly query the properties and parse them in pure posix shell, another important this is the need to check the terminal cells and rows against the X and Y pixels to calculate the X and Y pixel density of each cell for proper sixel sizing, also getting the background to properly display the images with montage on a properly colored background.

the commands to detect the properties along some echos to show them:

#!/bin/bash

timeout=0.25  # How long to wait for terminal to respond
              # to a control sequence (in seconds).

# Various terminal automatic configuration routines.

# Don't show escape sequences the terminal doesn't understand.
stty -echo          # Hush-a Mandara Ni Pari

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

# Query the terminal background and foreground colors.
old_ifs=$IFS
IFS=";:/"  read -a REPLY -r -s -t ${timeout} -d "\\" -p $'\e]11;?\e\\' >&2
if [[ ${REPLY[1]} =~ ^rgb ]]; then
  # Return value format: $'\e]11;rgb:ffff/0000/ffff\e\\'.
  # ImageMagick wants colors formatted as #ffff0000ffff.
  background='#'${REPLY[2]}${REPLY[3]}${REPLY[4]%%$'\e'*}
fi
IFS=$old_ifs

echo "$background"

# Send control sequence to query the sixel graphics geometry to
# find out how large of a sixel image can be shown.
old_ifs=$IFS
IFS=";"  read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?2;1;0S' >&2
if [[ ${REPLY[2]} -gt 0 ]]; then
  width=${REPLY[2]}
  height=${REPLY[3]}
fi
IFS=$old_ifs
# else

# Nope. Fall back to dtterm WindowOps to approximate sixel geometry.
old_ifs=$IFS
IFS=";" read -a REPLY -s -t ${timeout} -d "t" -p $'\e[14t' >&2
winwidth=${REPLY[2]}"x"${REPLY[1]}
IFS=$old_ifs
# fi

# returns ^[4;height;widtht
old_ifs=$IFS
IFS=";" read -a REPLY -s -t ${timeout} -d "t" -p $'\e[19t' >&2
dim=${REPLY[2]}"x"${REPLY[1]}
IFS=$old_ifs

echo "$width"
echo "$height"
echo "$winwidth"

echo "$dim"

a lot of this was taken directly from lsix

ideally some of these could be cached to not have the need for constantly queriying the properties on every image.

eylles commented 2 years ago

@trappedinspacetime this is some work i had pending but didn't push because i was busy with other stuff.

eylles commented 2 years ago

Hmmm it may be possible to not use bash, apparently modification of stty settings can be used to read answer sequences, but i'm too smooth brained to understand well what is going on.

trappedinspacetime commented 2 years ago

@eylles I'm not a bash expert but I like using bash scripts. I prefer using bash in scripting. I came across the following bug https://github.com/hackerb9/lsix/issues/54, so I didn't get far. I also have a health issue, so I may lag behind. I met an image-viewer script here https://github.com/vifm/vifm/issues/419#issuecomment-485918513

hackerb9 commented 2 years ago

Hmmm it may be possible to not use bash, apparently modification of stty settings can be used to read answer sequences, but i'm too smooth brained to understand well what is going on.

It's possible that not understanding stty is actually a sign that your brain is healthy.

Back in the olden days, before we had bash to do the heavy lifting, we used extremely ugly kludges via the stty command in the Bourne shell. However, stty was so brittle that people would often write a custom routine in C to handle the termios ioctl.

To get a flavour of what we had to do, here's an implementation I wrote in Python:

```python ## See man termios(3) for details on tcgetattr from termios import * def terminal_query(seq, delimiter=None, timeout=0.2): """ Given an escape SEQuence, and optionally a DELIMITER and a TIMEOUT, print SEQ to stderr and read a response from stdin until the character DELIMITER is read or TIMEOUT seconds is reached. Input that is read is returned to the calling function. If TIMEOUT is not specified, it default to 0.2. If DELIMITER is not specified, it defaults to the last character of SEQ. If neither DELIMITER nor SEQ are specified, then "" is returned. """ import sys, copy, posix if not delimiter and not seq: return "" if not seq: seq="" # Allow simply reading from terminal. # Responses usually end with the same character as the request. if not delimiter and len(seq)>0: delimiter=seq[-1] oldmode = tcgetattr(sys.stdin.fileno()) # tcgetattr returns [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] [ iflag, oflag, cflag, lflag, ispeed, ospeed, cc ] = oldmode ## CBREAK MODE: Read byte by byte. ## Cbreak is like Raw but allow ^C interrupt signal and does ## not clear OPOST (print newlines as carriage return + newline) # Do not transmogrify input in any way... iflag = iflag & ~(INPCK | ISTRIP | IXON | ICRNL | INLCR | IGNCR) # ... except do allow BREAK (^C) to flush queues and send SIGINT iflag = (iflag & ~IGNBRK) | BRKINT lflag = lflag & ~ICANON # Noncanonical: read by bytes, not lines lflag = lflag & ~ECHO # Do not echo characters received # Clear character size and disable parity checking cflag = cflag & ~(CSIZE | PARENB) cflag = cflag | CS8 # Set 8-bit character size ## Polling read (MIN == 0, TIME == 0) See termios(3). cc[VMIN] = 0 cc[VTIME] = 0 pollingread = copy.deepcopy([ iflag, oflag, cflag, lflag, ispeed, ospeed, cc ]) ## Read with timeout (MIN == 0, TIME > 0) See termios(3). cc[VMIN] = 0 cc[VTIME] = int(timeout*10+0.5) # Timeout is in tenths of a second readwithtimeout = copy.deepcopy([ iflag, oflag, cflag, lflag, ispeed, ospeed, cc ]) # Drain stdin in case there's junk from a prev req in there already. tcsetattr(sys.stdin.fileno(), TCSANOW, pollingread) while posix.read(sys.stdin.fileno(), 1024): debugprint(".", end='') output="" # String of output so far. c = None # Currently read character. try: # Next read() should timeout if no byte becomes available. tcsetattr(sys.stdin.fileno(), TCSANOW, readwithtimeout) print(seq, file=stderr, end='', flush=True) # Send Esc seq to terminal # Accumulate response in 'output' until delimiter or timeout while c != delimiter: c = posix.read(sys.stdin.fileno(), 1).decode() if c: output=output+c # debugprint("got", repr(c), "waiting for", repr(delimiter)) else: debugprint("read() returned 0 characters after", repr(seq)) # Timeout break finally: tcsetattr(sys.stdin.fileno(), TCSANOW, oldmode) if (debug): if output: debugprint("Terminal query received: ", repr(output)) if (c == delimiter): debugprint("Exited on delimiter", repr(c)) else: debugprint("Exited after timeout", repr(c)) return(output) ```

While it works, I am thankful that bash exists and I don't have to wrap my brain around such ugliness anymore.