diff --git a/kittens/icat/main.py b/kittens/icat/main.py index 410eb26fe..297fa3536 100644 --- a/kittens/icat/main.py +++ b/kittens/icat/main.py @@ -76,13 +76,14 @@ Remove all images currently displayed on the screen. --transfer-mode type=choices -choices=detect,file,stream +choices=detect,file,stream,memory default=detect Which mechanism to use to transfer images to the terminal. The default is to -auto-detect. :italic:`file` means to use a temporary file and :italic:`stream` -means to send the data via terminal escape codes. Note that if you use the -:italic:`file` transfer mode and you are connecting over a remote session then -image display will not work. +auto-detect. :italic:`file` means to use a temporary file, :italic:`memory` means +to use shared memory, :italic:`stream` means to send the data via terminal +escape codes. Note that if you use the :italic:`file` or :italic:`memory` transfer +modes and you are connecting over a remote session then image display will not +work. --detect-support @@ -622,4 +623,5 @@ elif __name__ == '__doc__': cd['usage'] = usage cd['options'] = options_spec cd['help_text'] = help_text + cd['short_desc'] = 'Display images in the terminal' cd['args_completion'] = CompletionSpec.from_string('type:file mime:image/* group:Images') diff --git a/shell-integration/ssh/kitty b/shell-integration/ssh/kitty index f1925e804..330400bc2 100755 --- a/shell-integration/ssh/kitty +++ b/shell-integration/ssh/kitty @@ -24,7 +24,7 @@ exec_kitty() { is_wrapped_kitten() { - wrapped_kittens="clipboard" + wrapped_kittens="clipboard icat" [ -n "$1" ] && { case " $wrapped_kittens " in *" $1 "*) printf "%s" "$1" ;; diff --git a/tools/cmd/icat/main.go b/tools/cmd/icat/main.go new file mode 100644 index 000000000..a8df4412b --- /dev/null +++ b/tools/cmd/icat/main.go @@ -0,0 +1,113 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package icat + +import ( + "fmt" + "os" + + "kitty/tools/cli" + "kitty/tools/tty" + "kitty/tools/tui/graphics" + "kitty/tools/tui/loop" + "kitty/tools/utils" +) + +var _ = fmt.Print + +var opts *Options +var lp *loop.Loop + +type transfer_mode int + +const ( + unknown transfer_mode = iota + unsupported + supported +) + +var transfer_by_file, transfer_by_memory, transfer_by_stream transfer_mode + +var temp_files_to_delete []string +var direct_query_id, file_query_id, memory_query_id uint32 +var stderr_is_tty bool + +func print_error(format string, args ...any) { + if lp == nil || !stderr_is_tty { + fmt.Fprintf(os.Stderr, format, args...) + fmt.Fprintln(os.Stderr) + } else { + lp.QueueWriteString(fmt.Sprintf(format, args...)) + lp.QueueWriteString("\r\n") + } +} + +func on_initialize() (string, error) { + var iid uint32 + g := func(t graphics.GRT_t, payload string) uint32 { + iid += 1 + g1 := &graphics.GraphicsCommand{} + g1.SetTransmission(t).SetAction(graphics.GRT_action_query).SetImageId(iid).SetDataWidth(1).SetDataHeight(1).SetFormat(graphics.GRT_format_rgb) + g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload)) + return iid + } + sz, err := lp.ScreenSize() + if err != nil { + return "", fmt.Errorf("Failed to query terminal for screen size with error: %w", err) + } + if sz.WidthPx == 0 || sz.HeightPx == 0 { + return "", fmt.Errorf("Terminal does not support reporting screen sizes via the TIOCGWINSZ ioctl") + } + direct_query_id = g(graphics.GRT_transmission_direct, "123") + tf, err := graphics.MakeTemp() + if err == nil { + file_query_id = g(graphics.GRT_transmission_tempfile, tf.Name()) + temp_files_to_delete = append(temp_files_to_delete, tf.Name()) + tf.Write([]byte{1, 2, 3}) + tf.Close() + } else { + transfer_by_file = unsupported + print_error("Failed to create temporary file for data transfer, file based transfer is disabled. Error: %v", err) + } + + return "", nil +} + +func on_finalize() string { + if len(temp_files_to_delete) > 0 { + for _, name := range temp_files_to_delete { + os.Remove(name) + } + } + return "" +} + +func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { + opts = o + stderr_is_tty = tty.IsTerminal(os.Stderr.Fd()) + if opts.PrintWindowSize { + t, err := tty.OpenControllingTerm() + if err != nil { + return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err) + } + sz, err := t.GetSize() + if err != nil { + return 1, fmt.Errorf("Failed to query terminal using TIOCGWINSZ with error: %w", err) + } + fmt.Printf("%dx%d", sz.Xpixel, sz.Ypixel) + return 0, nil + } + temp_files_to_delete = make([]string, 0, 8) + lp, err = loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking) + if err != nil { + return + } + lp.OnInitialize = on_initialize + lp.OnFinalize = on_finalize + + return 0, nil +} + +func EntryPoint(parent *cli.Command) { + create_cmd(parent, main) +} diff --git a/tools/cmd/tool/main.go b/tools/cmd/tool/main.go index 061cd9fb4..380d22181 100644 --- a/tools/cmd/tool/main.go +++ b/tools/cmd/tool/main.go @@ -9,6 +9,7 @@ import ( "kitty/tools/cmd/at" "kitty/tools/cmd/clipboard" "kitty/tools/cmd/edit_in_kitty" + "kitty/tools/cmd/icat" "kitty/tools/cmd/update_self" "kitty/tools/tui" ) @@ -26,6 +27,8 @@ func KittyToolEntryPoints(root *cli.Command) { edit_in_kitty.EntryPoint(root) // clipboard clipboard.EntryPoint(root) + // icat + icat.EntryPoint(root) // __hold_till_enter__ root.AddSubCommand(&cli.Command{ Name: "__hold_till_enter__", diff --git a/tools/tui/graphics/command.go b/tools/tui/graphics/command.go index 65918d091..4d7eb6ef7 100644 --- a/tools/tui/graphics/command.go +++ b/tools/tui/graphics/command.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "fmt" "io" + "os" "strconv" "strings" @@ -17,6 +18,10 @@ import ( var _ = fmt.Print +func MakeTemp() (*os.File, error) { + return os.CreateTemp("", "tty-graphics-protocol-*") +} + // Enums {{{ type GRT_a int