aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/msgviewer.go101
1 files changed, 99 insertions, 2 deletions
diff --git a/app/msgviewer.go b/app/msgviewer.go
index e45a6d3e..8d2e380a 100644
--- a/app/msgviewer.go
+++ b/app/msgviewer.go
@@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
+ "image"
"io"
"os"
"os/exec"
@@ -24,8 +25,28 @@ import (
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/go-opt"
+ "git.sr.ht/~rockorager/vaxis"
+ "git.sr.ht/~rockorager/vaxis/widgets/align"
+
+ // Image support
+ _ "image/jpeg"
+ _ "image/png"
+
+ _ "golang.org/x/image/bmp"
+ _ "golang.org/x/image/tiff"
+ _ "golang.org/x/image/webp"
)
+// All imported image types need to be explicitly stated here. We want to check
+// if we _can_ display something before we download it
+var supportedImageTypes = []string{
+ "image/jpeg",
+ "image/png",
+ "image/bmp",
+ "image/tiff",
+ "image/webp",
+}
+
var _ ProvidesMessages = (*MessageViewer)(nil)
type MessageViewer struct {
@@ -405,6 +426,11 @@ type PartViewer struct {
noFilter *ui.Grid
uiConfig *config.UIConfig
copying int32
+ inlineImg bool
+ image image.Image
+ graphic vaxis.Image
+ width int
+ height int
links []string
}
@@ -535,7 +561,27 @@ func NewPartViewer(
func (pv *PartViewer) SetSource(reader io.Reader) {
pv.source = reader
- pv.attemptCopy()
+ switch pv.inlineImg {
+ case true:
+ pv.decodeImage()
+ default:
+ pv.attemptCopy()
+ }
+}
+
+func (pv *PartViewer) decodeImage() {
+ atomic.StoreInt32(&pv.copying, copying)
+ go func() {
+ defer log.PanicHandler()
+ defer pv.Invalidate()
+ defer atomic.StoreInt32(&pv.copying, 0)
+ img, _, err := image.Decode(pv.source)
+ if err != nil {
+ log.Errorf("error decoding image: %v", err)
+ return
+ }
+ pv.image = img
+ }()
}
func (pv *PartViewer) attemptCopy() {
@@ -711,7 +757,13 @@ func (pv *PartViewer) Invalidate() {
func (pv *PartViewer) Draw(ctx *ui.Context) {
style := pv.uiConfig.GetStyle(config.STYLE_DEFAULT)
- if pv.filter == nil {
+ switch {
+ case pv.filter == nil && canInline(pv.part.FullMIMEType()) && pv.err == nil:
+ pv.inlineImg = true
+ case pv.filter == nil:
+ // No filter, can't inline, and/or we attempted to inline an image
+ // and resulted in an error (maybe because of a bad encoding or
+ // the terminal doesn't support any graphics protocol).
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
pv.noFilter.Draw(ctx)
return
@@ -728,12 +780,48 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
if pv.term != nil {
pv.term.Draw(ctx)
}
+ if pv.image != nil && (pv.resized(ctx) || pv.graphic == nil) {
+ // This path should only occur on resizes or the first pass
+ // after the image is downloaded and could be slow due to
+ // encoding the image to either sixel or uploading via the kitty
+ // protocol. Generally it's pretty fast since we will only ever
+ // be downsizing images
+ vx := ctx.Window().Vx
+ if pv.graphic == nil {
+ var err error
+ pv.graphic, err = vx.NewImage(pv.image)
+ if err != nil {
+ log.Errorf("Couldn't create image: %v", err)
+ return
+ }
+ }
+ pv.graphic.Resize(pv.width, pv.height)
+ }
+ if pv.graphic != nil {
+ w, h := pv.graphic.CellSize()
+ win := align.Center(ctx.Window(), w, h)
+ pv.graphic.Draw(win)
+ }
}
func (pv *PartViewer) Cleanup() {
if pv.term != nil {
pv.term.Close()
}
+ if pv.graphic != nil {
+ pv.graphic.Destroy()
+ }
+}
+
+func (pv *PartViewer) resized(ctx *ui.Context) bool {
+ w := ctx.Width()
+ h := ctx.Height()
+ if pv.width != w || pv.height != h {
+ pv.width = w
+ pv.height = h
+ return true
+ }
+ return false
}
func (pv *PartViewer) Event(event tcell.Event) bool {
@@ -779,3 +867,12 @@ func (hv *HeaderView) Draw(ctx *ui.Context) {
func (hv *HeaderView) Invalidate() {
ui.Invalidate()
}
+
+func canInline(mime string) bool {
+ for _, ext := range supportedImageTypes {
+ if mime == ext {
+ return true
+ }
+ }
+ return false
+}