htermで画像のインライン表示・ダウンロード
はじめに
ChromeからSSH接続ができる、Secure Shellという拡張機能をよく使っています。
このSecure ShellはGoogleが開発しているもので、内部でhtermというターミナルエミュレータが使われています。このhtermはiTerm2におけるOSC 1337コマンドを一部実装しており、これを使うことで画像のインライン表示・ファイルのダウンロードが可能です。このエントリではGoでこれらの挙動を検証します。
作成したデモは下記のレポジトリにあります。
github.com
OSC 1337
Mac向けのターミナルエミュレータであるiTerm2は、独自のエスケープシーケンスを定義しています。OSC 1337はその一つです。
Proprietary Escape Codes - Documentation - iTerm2 - macOS Terminal Replacement
OSC 1337は様々な仕様を含みますが、htermでは「画像の表示」「ファイル転送」の2つが実装されています。
hterm and Secure Shell - hterm Control Sequences
画像のインライン表示
ターミナルに画像がインライン表示されます。htermで表示できるのはChromeが解釈できるメディアに限られるようです (それはそう)。
ファイル転送(ダウンロード)
SSH接続先のファイルがクライアントにダウンロードされます。画像以外のファイルもダウンロードできます。
仕様
iTerm2におけるOSC 1337の画像表示・ファイル転送の仕様は以下に書かれています。
Images - Documentation - iTerm2 - macOS Terminal Replacement
また、htermにおける仕様は以下に書かれています。
hterm and Secure Shell - hterm Control Sequences
画像表示・ファイル転送共に、下記のエスケープシーケンスを含む文字列を出力します。
ESC]1337;File=[arguments]:[base64 data]BEL
ここでarguments
は下記のものです。
- name: The base64 encoded name of the file or other human readable text.
- size: How many bytes in the base64 data (for transfer progress).
- width: The display width specification (see below). Defaults to auto.
- height: The display height specification (see below). Defaults to auto.
- preserveAspectRatio: If 0, scale/stretch the display to fit the space. If 1 (the default), fill the display as much as possible without stretching.
- inline: If 0 (the default), download the file instead of displaying it. If 1, display the file in the terminal.
- align: Set the display alignment with left (the default), right, or center.
- type: Set the MIME type of the content. Auto-detected otherwise.
hterm and Secure Shell - hterm Control Sequences
例えば、ファイル名がsample.png
(Base64エンコードするとc2FtcGxlLnBuZw==
)で、ダウンロードではなくインライン表示をする場合、arguments
は以下のようになります。
name=c2FtcGxlLnBuZw==;inline=1;
また、base64 data
は画像やその他のファイルのデータをBase64エンコードしたものです。
アプリケーション例
OSC 1337の画像表示を使ったアプリケーションをいくつか挙げます。
- htermの公式サンプル
hterm/etc/hterm-show-file.sh - apps/libapps - Git at Google - mattn/longcat
github.com - rs/jplot
こちらはまだ手元で検証できていませんが/osc/iterm2.go
でOSC 1337の画像表示を使っています。 github.com
Goでの実装
コードは下記レポジトリにあります。Goで書きました。 github.com
osc1337
パッケージ
package osc1337 import ( "encoding/base64" "fmt" "io" ) type Encoder struct { W io.Writer Name []byte Size int Width string Height string PreserveAspectRatio bool Inline bool Align string Type string } func NewEncoder() Encoder { return Encoder{ Width: "auto", Height: "auto", PreserveAspectRatio: true, Align: "left", Inline: false, } } func (e Encoder) Encode(img []byte) { if e.W == nil || img == nil { return } fmt.Fprintf(e.W, "\x1b]1337;File=name=%s", base64.StdEncoding.EncodeToString(e.Name)) if e.Size != 0 { fmt.Fprintf(e.W, ";size=%d", e.Size) } inline := 0 if e.Inline { inline = 1 } preserveAspectRatio := 0 if e.PreserveAspectRatio { preserveAspectRatio = 1 } fmt.Fprintf(e.W, ";width=%s;height=%s;preserveAspectRatio=%d;inline=%d;align=%s;type=%s:%s\a\n", e.Width, e.Height, preserveAspectRatio, inline, e.Align, e.Type, base64.StdEncoding.EncodeToString(img)) }
main
パッケージ
package main import ( "bufio" "bytes" "io" "os" "sample/osc1337" ) func main() { fname := "image.png" body, err := importFile(fname) if err != nil { panic(err.Error()) } var buf bytes.Buffer e := osc1337.NewEncoder() // get default values e.W = &buf e.Name = []byte(fname) e.Width = "50%" e.Inline = true e.Type = "image/png" e.Encode(body) os.Stdout.Write(buf.Bytes()) os.Stdout.Sync() } func importFile(fname string) ([]byte, error) { f, err := os.Open(fname) if err != nil { return nil, err } defer f.Close() var r io.Reader = bufio.NewReader(f) body, err := io.ReadAll(r) if err != nil { return nil, err } return body, nil }
これをビルドして実行すると、実行ファイルと同じディレクトリにあるiamge.png
がインライン表示されます (冒頭で紹介した画像です)。
main.go
の以下の部分で設定を書き換えることができます。
e := osc1337.NewEncoder() // get default values e.W = &buf e.Name = []byte(fname) e.Width = "50%" e.Inline = true e.Type = "image/png"
各種設定による挙動を見てみましょう。
インライン表示・大きさ指定
- width = 100%
e := osc1337.NewEncoder() // get default values e.W = &buf e.Name = []byte(fname) e.Width = "100%" e.Inline = true e.Type = "image/png"
インライン表示・Align
- align = center
e := osc1337.NewEncoder() // get default values e.W = &buf e.Name = []byte(fname) e.Width = "50%" e.Inline = true e.Align = "center" e.Type = "image/png"
インライン表示・アスペクト比
- preserveAspectRatio = 0
e := osc1337.NewEncoder() e.W = &buf e.Name = []byte(fname) e.Width = "50%" e.Height = "10%" e.PreserveAspectRatio = false e.Inline = true e.Type = "image/png"
ダウンロード
- pngのダウンロード
e := osc1337.NewEncoder() e.W = &buf e.Name = []byte(fname) e.Type = "image/png"
main.go
のダウンロード
fname := "main.go" <中略> e := osc1337.NewEncoder() e.W = &buf e.Name = []byte(fname) e.Type = "image/png"
おわりに
htermでインライン表示・ダウンロードの検証ができました。GoのCLIツールで使っていきたいですね。