My scripts for startup, dmenu, and the command line
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

lsix 12 KiB

4 år sedan
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. #!/usr/bin/env bash
  2. # lsix: like ls, but for images.
  3. # Shows thumbnails of images with titles directly in terminal.
  4. # Requirements: just ImageMagick (and a Sixel terminal, of course)
  5. # Version 1.7.1
  6. # B9 April 2020
  7. # See end of file for USAGE.
  8. # The following defaults may be overridden if autodetection succeeds.
  9. numcolors=16 # Default number of colors in the palette.
  10. background=white # Default montage background.
  11. foreground=black # Default text color.
  12. width=800 # Default width of screen in pixels.
  13. # Feel free to edit these defaults to your liking.
  14. tilesize=120 # Width and height of each tile in the montage.
  15. tilewidth=$tilesize # (or specify separately, if you prefer)
  16. tileheight=$tilesize
  17. # If you get questionmarks for Unicode filenames, try using a different font.
  18. # You can list fonts available using `convert -list font`.
  19. #fontfamily=Droid-Sans-Fallback # Great Asian font coverage
  20. #fontfamily=Dejavu-Sans # Wide coverage, comes with GNU/Linux
  21. #fontfamily=Mincho # Wide coverage, comes with MS Windows
  22. # Default font size is based on width of each tile in montage.
  23. fontsize=$((tilewidth/10))
  24. #fontsize=16 # (or set the point size directly, if you prefer)
  25. timeout=0.25 # How long to wait for terminal to respond
  26. # to a control sequence (in seconds).
  27. # Sanity checks and compatibility
  28. if [[ ${BASH_VERSINFO[0]} -eq 3 ]]; then
  29. if bash --version | head -1 | grep -q "version 3"; then
  30. cat <<-EOF >&2
  31. Error: The version of Bash is extremely out of date.
  32. (2007, the same year Steve Jobs announced the iPhone!)
  33. This is almost always due to Apple's MacOS being silly.
  34. Please let Apple know that their users expect current UNIX tools.
  35. In the meantime, try using "brew install bash".
  36. EOF
  37. exit 1
  38. else
  39. exec bash "$0" "$@" || echo "Exec failed" >&2
  40. exit 1
  41. fi
  42. fi
  43. if ! command -v montage >/dev/null; then
  44. echo "Please install ImageMagick" >&2
  45. exit 1
  46. fi
  47. if command -v gsed >/dev/null; then
  48. alias sed=gsed # Use GNU sed for MacOS & BSD
  49. fi
  50. cleanup() {
  51. echo -n $'\e\\' # Escape sequence to stop SIXEL.
  52. stty echo # Reset terminal to show characters.
  53. exit 0
  54. }
  55. trap cleanup SIGINT SIGHUP SIGABRT EXIT
  56. autodetect() {
  57. # Various terminal automatic configuration routines.
  58. # Don't show escape sequences the terminal doesn't understand.
  59. stty -echo # Hush-a Mandara Ni Pari
  60. # IS TERMINAL SIXEL CAPABLE? # Send Device Attributes
  61. IFS=";" read -a REPLY -s -t 1 -d "c" -p $'\e[c' >&2
  62. for code in "${REPLY[@]}"; do
  63. if [[ $code == "4" ]]; then
  64. hassixel=yup
  65. break
  66. fi
  67. done
  68. # YAFT is vt102 compatible, cannot respond to vt220 escape sequence.
  69. if [[ "$TERM" == yaft* ]]; then hassixel=yeah; fi
  70. if [[ -z "$hassixel" ]]; then
  71. cat <<-EOF >&2
  72. Error: Your terminal does not report having sixel graphics support.
  73. Please use a sixel capable terminal, such as xterm -ti vt340, or
  74. ask your terminal manufacturer to add sixel support.
  75. You may test your terminal by viewing a single image, like so:
  76. convert foo.jpg -geometry 800x480 sixel:-
  77. If your terminal actually does support sixel, please file a bug
  78. report at http://github.com/hackerb9/lsix/issues
  79. EOF
  80. read -s -t 1 -d "c" -p $'\e[c' >&2
  81. if [[ "$REPLY" ]]; then
  82. echo
  83. cat -v <<< "(Please mention device attribute codes: ${REPLY}c)"
  84. fi
  85. exit 1
  86. fi
  87. # ENABLE SIXEL SCROLLING so image will appear right after cursor.
  88. if [[ $TERM != "mlterm" ]]; then
  89. echo -ne $'\e[?80h'
  90. else
  91. # Except... mlterm (as of 3.5.0) has a bug that reverses the sense
  92. echo -ne $'\e[?80l'
  93. fi
  94. # TERMINAL COLOR AUTODETECTION.
  95. # Find out how many color registers the terminal has
  96. IFS=";" read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?1;1;0S' >&2
  97. [[ ${REPLY[1]} == "0" ]] && numcolors=${REPLY[2]}
  98. # Bug workaround: mlterm does not report number of colors.
  99. if [[ $TERM =~ mlterm ]]; then numcolors=1024; fi
  100. # YAFT is vt102 compatible, cannot respond to vt220 escape sequence.
  101. if [[ "$TERM" == yaft* ]]; then numcolors=256; fi
  102. # Increase colors, if needed
  103. if [[ $numcolors -lt 256 ]]; then
  104. # Attempt to set the number of colors to 256.
  105. # This will work for xterm, but fail on a real vt340.
  106. IFS=";" read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?1;3;256S' >&2
  107. [[ ${REPLY[1]} == "0" ]] && numcolors=${REPLY[2]}
  108. fi
  109. # Query the terminal background and foreground colors.
  110. IFS=";:/" read -a REPLY -r -s -t ${timeout} -d "\\" -p $'\e]11;?\e\\' >&2
  111. if [[ ${REPLY[1]} =~ ^rgb ]]; then
  112. # Return value format: $'\e]11;rgb:ffff/0000/ffff\e\\'.
  113. # ImageMagick wants colors formatted as #ffff0000ffff.
  114. background='#'${REPLY[2]}${REPLY[3]}${REPLY[4]%%$'\e'*}
  115. IFS=";:/" read -a REPLY -r -s -t ${timeout} -d "\\" -p $'\e]10;?\e\\' >&2
  116. if [[ ${REPLY[1]} =~ ^rgb ]]; then
  117. foreground='#'${REPLY[2]}${REPLY[3]}${REPLY[4]%%$'\e'*}
  118. # Check for "Reverse Video" (DECSCNM screen mode).
  119. IFS=";?$" read -a REPLY -s -t ${timeout} -d "y" -p $'\e[?5$p'
  120. if [[ ${REPLY[2]} == 1 || ${REPLY[2]} == 3 ]]; then
  121. temp=$foreground
  122. foreground=$background
  123. background=$temp
  124. fi
  125. fi
  126. fi
  127. # YAFT is vt102 compatible, cannot respond to vt220 escape sequence.
  128. if [[ "$TERM" == yaft* ]]; then background=black; foreground=white; fi
  129. # Send control sequence to query the sixel graphics geometry to
  130. # find out how large of a sixel image can be shown.
  131. IFS=";" read -a REPLY -s -t ${timeout} -d "S" -p $'\e[?2;1;0S' >&2
  132. if [[ ${REPLY[2]} -gt 0 ]]; then
  133. width=${REPLY[2]}
  134. else
  135. # Nope. Fall back to dtterm WindowOps to approximate sixel geometry.
  136. IFS=";" read -a REPLY -s -t ${timeout} -d "t" -p $'\e[14t' >&2
  137. if [[ $? == 0 && ${REPLY[2]} -gt 0 ]]; then
  138. width=${REPLY[2]}
  139. fi
  140. fi
  141. # BUG WORKAROUND: XTerm cannot show images wider than 1000px.
  142. # Remove this hack once XTerm gets fixed. Last checked: XTerm(327)
  143. if [[ $TERM =~ xterm && $width -ge 1000 ]]; then width=1000; fi
  144. # Space on either side of each tile is less than 0.5% of total screen width
  145. tilexspace=$((width/201))
  146. tileyspace=$((tilexspace/2))
  147. # Figure out how many tiles we can fit per row. ("+ 1" is for -shadow).
  148. numtiles=$((width/(tilewidth + 2*tilexspace + 1)))
  149. }
  150. main() {
  151. # Discover and setup the terminal
  152. autodetect
  153. if [[ $# == 0 ]]; then
  154. # No command line args? Use a sorted list of image files in CWD.
  155. shopt -s nullglob nocaseglob nocasematch
  156. set -- *{jpg,jpeg,png,gif,tiff,tif,p?m,x[pb]m,bmp,ico,svg,eps}
  157. [[ $# != 0 ]] || exit
  158. readarray -t < <(printf "%s\n" "$@" | sort)
  159. # Only show first frame of animated GIFs if filename not specified.
  160. for x in ${!MAPFILE[@]}; do
  161. if [[ ${MAPFILE[$x]} =~ gif$ ]]; then
  162. MAPFILE[$x]=":${MAPFILE[$x]}[0]"
  163. fi
  164. done
  165. set -- "${MAPFILE[@]}"
  166. else
  167. # Command line args specified. Check for directories.
  168. lsix=$(realpath "$0")
  169. for arg; do
  170. if [ -d "$arg" ]; then
  171. echo Recursing on $arg
  172. (cd "$arg"; $lsix)
  173. else
  174. nodirs+=($arg)
  175. fi
  176. done
  177. set -- "${nodirs[@]}"
  178. fi
  179. # Resize on load: Save memory by appending this suffix to every filename.
  180. resize="[${tilewidth}x${tileheight}]"
  181. imoptions="-tile ${numtiles}x1" # Each montage is 1 row x $numtiles columns
  182. imoptions+=" -geometry ${tilewidth}x${tileheight}>+${tilexspace}+${tileyspace}" # Size of each tile and spacing
  183. imoptions+=" -background $background -fill $foreground" # Use terminal's colors
  184. imoptions+=" -auto-orient " # Properly rotate JPEGs from cameras
  185. if [[ $numcolors -gt 16 ]]; then
  186. imoptions+=" -shadow " # Just for fun :-)
  187. fi
  188. # See top of this file to change fontfamily and fontsize.
  189. [[ "$fontfamily" ]] && imoptions+=" -font $fontfamily "
  190. [[ "$fontsize" ]] && imoptions+=" -pointsize $fontsize "
  191. # Create and display montages one row at a time.
  192. while [ $# -gt 0 ]; do
  193. # While we still have images to process...
  194. onerow=()
  195. goal=$(($# - numtiles)) # How many tiles left after this row
  196. while [ $# -gt 0 -a $# -gt $goal ]; do
  197. len=${#onerow[@]}
  198. onerow[len++]="-label"
  199. onerow[len++]=$(processlabel "$1")
  200. onerow[len++]="$1"
  201. shift
  202. done
  203. montage "${onerow[@]}" $imoptions gif:- \
  204. | convert - -colors $numcolors sixel:-
  205. done
  206. }
  207. processlabel() {
  208. # This routine is all about appeasing ImageMagick.
  209. # 1. Remove silly [0] suffix and : prefix.
  210. # 2. Quote percent backslash, and at sign.
  211. # 3. Replace control characters with question marks.
  212. # 4. If a filename is too long, remove extension (.jpg).
  213. # 5. Split long filenames with newlines (recursively)
  214. span=15 # filenames longer than span will be split
  215. echo -n "$1" |
  216. sed 's|^:||; s|\[0]$||;' | tr '[:cntrl:]' '?' |
  217. awk -v span=$span -v ORS="" '
  218. function halve(s, l,h) { # l and h are locals
  219. l=length(s); h=int(l/2);
  220. if (l <= span) { return s; }
  221. return halve(substr(s, 1, h)) "\n" halve(substr(s, h+1));
  222. }
  223. {
  224. if ( length($0) > span ) gsub(/\..?.?.?.?$/, "");
  225. print halve($0);
  226. }
  227. ' |
  228. sed 's|%|%%|g; s|\\|\\\\|g; s|@|\\@|g;'
  229. }
  230. ####
  231. main "$@"
  232. # Send an escape sequence and wait for a response from the terminal
  233. # so that the program won't quit until images have finished transferring.
  234. read -s -t 60 -d "c" -p $'\e[c' >&2
  235. ######################################################################
  236. # NOTES:
  237. # Usage: lsix [ FILES ... ]
  238. # * FILES can be any image file that ImageMagick can handle.
  239. #
  240. # * If no FILES are specified the most common file extensions are tried.
  241. # (For now, lsix only searches the current working directory.)
  242. #
  243. # * Non-bitmap graphics often work fine (.svg, .eps, .pdf, .xcf).
  244. #
  245. # * Files containing multiple images (e.g., animated GIFs) will show
  246. # all the images if the filename is specified at the command line.
  247. # Only the first frame will be shown if "lsix" is called with no
  248. # arguments.
  249. #
  250. # * Because this uses escape sequences, it works seamlessly through ssh.
  251. #
  252. # * If your terminal supports reporting the background and foreground
  253. # color, lsix will use those for the montage background and text fill.
  254. #
  255. # * If your terminal supports changing the number of color registers
  256. # to improve the picture quality, lsix will do so.
  257. # * Only software needed is ImageMagick (e.g., apt-get install imagemagick).
  258. # Your terminal must support SIXEL graphics. E.g.,
  259. #
  260. # xterm -ti vt340
  261. # * To make vt340 be the default xterm type, set this in .Xresources:
  262. #
  263. # ! Allow sixel graphics. (Try: "convert -colors 16 foo.jpg sixel:-").
  264. # xterm*decTerminalID : vt340
  265. # * Xterm does not support reporting the screen size in pixels unless
  266. # you add this to your .Xresources:
  267. #
  268. # ! Allow xterm to read the terminal window size (op #14)
  269. # xterm*allowWindowOps : False
  270. # xterm*disallowedWindowOps : 1,2,3,4,5,6,7,8,9,11,13,18,19,20,21,GetSelection,SetSelection,SetWinLines,SetXprop
  271. # * Be cautious using lsix on videos (lsix *.avi) as ImageMagick will
  272. # try to make a montage of every single frame and likely exhaust
  273. # your memory and/or your patience.
  274. # BUGS
  275. # * Sort order is a bit strange.
  276. # * ImageMagick's Montage doesn't handle long filenames nicely.
  277. # * Some transparent images (many .eps files) presume a white background
  278. # and will not show up if your terminal's background is black.
  279. # * This file is getting awfully long for a one line kludge. :-)
  280. # LICENSE INFORMATION
  281. # (AKA, You know your kludge has gotten out of hand when...)
  282. # Dual license:
  283. # * You have all the freedoms permitted to you under the
  284. # GNU GPL >=3. (See the included LICENSE file).
  285. # * Additionally, this program can be used under the terms of whatever
  286. # license 'xterm' is using (now or in the future). This is primarily
  287. # so that, if the xterm maintainer (currently Thomas E. Dickey) so
  288. # wishes, this program may be included with xterm as a Sixel test.
  289. # However, anyone who wishes to take advantage of this is free to do so.