Allow using many image formats in RC commands

This commit is contained in:
Kovid Goyal 2022-09-01 21:31:31 +05:30
parent fa7a6dfd4a
commit 905c4d641c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 51 additions and 26 deletions

1
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
)

4
go.sum
View File

@ -20,7 +20,11 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -6,7 +6,7 @@
from dataclasses import dataclass
from typing import (
TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Iterable, Iterator,
List, NoReturn, Optional, Set, Tuple, Type, Union, cast
List, NoReturn, Optional, Set, Tuple, Type, Union, cast, Mapping
)
from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec
@ -88,6 +88,7 @@ def __call__(self, key: str, opt_name: Optional[str] = None, missing: Any = None
PayloadType = Optional[Union[CmdReturnType, CmdGenerator]]
PayloadGetType = PayloadGetter
ArgsType = List[str]
ImageCompletion: Dict[str, Tuple[str, Tuple[str, ...]]] = {'files': ('Images', ('*.png', '*.jpg', '*.jpeg', '*.webp', '*.gif', '*.bmp', '*.tiff'))}
MATCH_WINDOW_OPTION = '''\
@ -183,7 +184,7 @@ class ArgsHandling:
json_field: str = ''
count: Optional[int] = None
spec: str = ''
completion: Optional[Dict[str, Tuple[str, Union[Callable[[], Iterable[str]], Tuple[str, ...]]]]] = None
completion: Optional[Mapping[str, Tuple[str, Union[Callable[[], Iterable[str]], Tuple[str, ...]]]]] = None
value_if_unspecified: Tuple[str, ...] = ()
minimum_count: int = -1
first_rest: Optional[Tuple[str, str]] = None

View File

@ -9,8 +9,9 @@
from kitty.types import AsyncResponse
from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, CmdGenerator, NamedTemporaryFile,
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window
MATCH_WINDOW_OPTION, ArgsType, Boss, CmdGenerator, ImageCompletion,
NamedTemporaryFile, PayloadGetType, PayloadType, RCOptions, RemoteCommand,
ResponseType, Window
)
if TYPE_CHECKING:
@ -62,8 +63,8 @@ class SetBackgroundImage(RemoteCommand):
Don't wait for a response from kitty. This means that even if setting the background image
failed, the command will exit with a success code.
''' + '\n\n' + MATCH_WINDOW_OPTION
args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(args[0])', completion={
'files': ('PNG Images', ('*.png',))})
args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(args[0])',
completion=ImageCompletion)
reads_streaming_data = True
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:

View File

@ -10,8 +10,9 @@
from kitty.types import AsyncResponse
from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, CmdGenerator, NamedTemporaryFile,
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window
MATCH_WINDOW_OPTION, ArgsType, Boss, CmdGenerator, ImageCompletion,
NamedTemporaryFile, PayloadGetType, PayloadType, RCOptions, RemoteCommand,
ResponseType, Window
)
if TYPE_CHECKING:
@ -58,8 +59,7 @@ class SetWindowLogo(RemoteCommand):
Don't wait for a response from kitty. This means that even if setting the image
failed, the command will exit with a success code.
'''
args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(args[0])', completion={
'files': ('PNG Images', ('*.png',))})
args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(args[0])', completion=ImageCompletion)
reads_streaming_data = True
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:

View File

@ -3,12 +3,20 @@
package at
import (
"bytes"
"encoding/base64"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
"image/png"
"io"
"net/http"
"os"
"strings"
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
)
type struct_with_data interface {
@ -31,19 +39,31 @@ func read_window_logo(path string) (func(io_data *rc_io_data) (bool, error), err
if err != nil {
return nil, err
}
buf := make([]byte, 2048)
n, err := f.Read(buf)
if err != nil && err != io.EOF {
f.Close()
return nil, err
var image_data_stream io.Reader
image_data_stream = f
config, format, ierr := image.DecodeConfig(f)
if ierr != nil {
return nil, fmt.Errorf("%s is not a supported image format", path)
}
buf = buf[:n]
f.Seek(0, 0)
if http.DetectContentType(buf) != "image/png" {
if format != "png" {
f.Seek(0, 0)
img, _, err := image.Decode(f)
if err != nil {
f.Close()
}
f.Close()
return nil, fmt.Errorf("%s is not a PNG image", path)
b := bytes.Buffer{}
b.Grow(config.Height * config.Width * 4)
err = png.Encode(&b, img)
if err != nil {
return nil, err
}
image_data_stream = &b
}
is_first_call := true
buf := make([]byte, 2048)
return func(io_data *rc_io_data) (bool, error) {
if is_first_call {
@ -51,18 +71,16 @@ func read_window_logo(path string) (func(io_data *rc_io_data) (bool, error), err
} else {
io_data.rc.Stream = false
}
if len(buf) == 0 {
set_payload_data(io_data, "")
io_data.rc.Stream = false
return true, nil
}
set_payload_data(io_data, base64.StdEncoding.EncodeToString(buf))
buf = buf[:cap(buf)]
n, err := f.Read(buf)
n, err := image_data_stream.Read(buf)
if err != nil && err != io.EOF {
return false, err
}
buf = buf[:n]
set_payload_data(io_data, base64.StdEncoding.EncodeToString(buf))
if err == io.EOF {
return true, nil
}
return false, nil
}, nil
}