Skip to content

Commit 3739f29

Browse files
authored
Merge pull request #9 from a-priestley/sixel
feat: Add sixel support
2 parents eac0616 + f566d46 commit 3739f29

File tree

1 file changed

+157
-13
lines changed

1 file changed

+157
-13
lines changed

wallhaven/cli.go

Lines changed: 157 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"os/exec"
77
"path/filepath"
8+
"strconv"
89
"strings"
910

1011
"github.com/spf13/cobra"
@@ -209,21 +210,22 @@ func Preview(cmd *cobra.Command, args []string) error {
209210

210211
text := args[0]
211212

213+
if strings.Contains(text, "-->") || strings.Contains(text, "<--") {
214+
return nil
215+
}
216+
212217
fzf_preview_lines := os.Getenv("FZF_PREVIEW_LINES")
213218
fzf_preview_columns := os.Getenv("FZF_PREVIEW_COLUMNS")
214-
_, exists := os.LookupEnv("KITTY_WINDOW_ID")
215-
216-
path, _ := exec.LookPath("chafa")
217219

218-
if strings.Contains(text, "-->") {
219-
return nil
220-
} else if strings.Contains(text, "<--") {
221-
return nil
222-
}
220+
// Check for terminal capabilities
221+
kittySupport := os.Getenv("KITTY_WINDOW_ID") != ""
222+
sixelSupport := checkSixelSupport()
223223

224224
url := re.FindStringSubmatch(text)[1]
225225

226-
if exists {
226+
// Priority: Kitty > Sixel > Chafa
227+
switch {
228+
case kittySupport:
227229
command = exec.Command(
228230
"kitty",
229231
"icat",
@@ -235,15 +237,71 @@ func Preview(cmd *cobra.Command, args []string) error {
235237
"--scale-up",
236238
url,
237239
)
238-
} else if path != "" {
240+
241+
case sixelSupport:
242+
// Download image for sixel conversion
239243
err := DLSave(url, &config.TempFolder)
240244
if err != nil {
241245
return err
242246
}
243247

248+
// Get terminal dimensions
249+
width, height, err := getPreviewPaneDimensions()
250+
if err != nil {
251+
// Fallback to FZF environment variables if available
252+
if fzfLines := os.Getenv("FZF_PREVIEW_LINES"); fzfLines != "" {
253+
if fzfCols := os.Getenv("FZF_PREVIEW_COLUMNS"); fzfCols != "" {
254+
width = fzfCols
255+
height = fzfLines
256+
}
257+
} else {
258+
// Last resort fallback
259+
width = "80"
260+
height = "24"
261+
}
262+
}
263+
244264
SplitUrl := strings.Split(url, "/")
245265
FileName := SplitUrl[len(SplitUrl)-1]
266+
file = filepath.Join(config.TempFolder, FileName)
267+
268+
// Convert terminal cells to pixels for sixel
269+
pixelWidth, pixelHeight := cellsToPixels(width, height)
270+
271+
// Try img2sixel first (better quality)
272+
if path, err := exec.LookPath("img2sixel"); err == nil {
273+
command = exec.Command(
274+
path,
275+
"-w", pixelWidth,
276+
file,
277+
)
278+
} else if path, err := exec.LookPath("magick"); err == nil {
279+
// Fallback to ImageMagick convert
280+
command = exec.Command(
281+
path,
282+
file,
283+
"-resize", fmt.Sprintf("%sx%s>", pixelWidth, pixelHeight),
284+
"-quality", "100",
285+
"sixel:-",
286+
)
287+
} else {
288+
return fmt.Errorf("sixel support detected but no converter found (install libsixel or imagemagick)")
289+
}
290+
291+
default:
292+
// Fallback to chafa
293+
path, err := exec.LookPath("chafa")
294+
if err != nil {
295+
return fmt.Errorf("no supported image preview method found")
296+
}
246297

298+
err = DLSave(url, &config.TempFolder)
299+
if err != nil {
300+
return err
301+
}
302+
303+
SplitUrl := strings.Split(url, "/")
304+
FileName := SplitUrl[len(SplitUrl)-1]
247305
file = filepath.Join(config.TempFolder, FileName)
248306

249307
command = exec.Command(
@@ -252,20 +310,106 @@ func Preview(cmd *cobra.Command, args []string) error {
252310
fmt.Sprintf("--size=%sx%s", fzf_preview_columns, fzf_preview_lines),
253311
"--clear",
254312
)
255-
} else {
256-
return fmt.Errorf("You either need chafa or the kitty terminal.")
257313
}
258314

259315
command.Stdout = os.Stdout
260316
command.Stderr = os.Stderr
261317

262318
if err := command.Run(); err != nil {
263-
return fmt.Errorf("failed to execute command: %s", err)
319+
return fmt.Errorf("failed to execute preview command: %s", err)
264320
}
265321

322+
// Clean up temporary file if created
266323
if file != "" {
267324
return os.Remove(file)
268325
}
269326

270327
return nil
271328
}
329+
330+
// getPreviewPaneDimensions attempts to get the actual preview pane size
331+
func getPreviewPaneDimensions() (string, string, error) {
332+
// Method 1: Try to get terminal size using stty
333+
cmd := exec.Command("stty", "size")
334+
cmd.Stdin = os.Stdin
335+
output, err := cmd.Output()
336+
if err == nil {
337+
parts := strings.Fields(string(output))
338+
if len(parts) == 2 {
339+
// fzf preview typically uses ~40% of terminal width
340+
// Adjust these percentages based on your fzf configuration
341+
totalHeight, _ := strconv.Atoi(parts[0])
342+
totalWidth, _ := strconv.Atoi(parts[1])
343+
344+
// Assuming default fzf preview layout
345+
previewHeight := totalHeight - 3 // Account for fzf interface
346+
previewWidth := int(float64(totalWidth) * 0.5) // Half screen by default
347+
348+
return strconv.Itoa(previewWidth), strconv.Itoa(previewHeight), nil
349+
}
350+
}
351+
352+
// Method 2: Use tput
353+
widthCmd := exec.Command("tput", "cols")
354+
heightCmd := exec.Command("tput", "lines")
355+
356+
widthOut, errW := widthCmd.Output()
357+
heightOut, errH := heightCmd.Output()
358+
359+
if errW == nil && errH == nil {
360+
width, _ := strconv.Atoi(strings.TrimSpace(string(widthOut)))
361+
height, _ := strconv.Atoi(strings.TrimSpace(string(heightOut)))
362+
363+
// Adjust for fzf preview pane (customize based on your layout)
364+
previewWidth := int(float64(width) * 0.5)
365+
previewHeight := height - 3
366+
367+
return strconv.Itoa(previewWidth), strconv.Itoa(previewHeight), nil
368+
}
369+
370+
return "", "", fmt.Errorf("could not determine terminal dimensions")
371+
}
372+
373+
// cellsToPixels converts terminal cell dimensions to pixel dimensions
374+
func cellsToPixels(cellWidth, cellHeight string) (string, string) {
375+
// Default cell dimensions (can be made configurable)
376+
cellPixelWidth := 9 // Typical terminal cell width in pixels
377+
cellPixelHeight := 18 // Typical terminal cell height in pixels
378+
379+
// Check for common terminal environment variables
380+
if os.Getenv("TERM_PROGRAM") == "iTerm.app" {
381+
cellPixelWidth = 8
382+
cellPixelHeight = 16
383+
}
384+
385+
width, _ := strconv.Atoi(cellWidth)
386+
height, _ := strconv.Atoi(cellHeight)
387+
388+
pixelWidth := width * cellPixelWidth
389+
pixelHeight := height * cellPixelHeight
390+
391+
// Apply some padding to ensure image fits well
392+
pixelWidth = int(float64(pixelWidth) * 0.95)
393+
pixelHeight = int(float64(pixelHeight) * 0.95)
394+
395+
return strconv.Itoa(pixelWidth), strconv.Itoa(pixelHeight)
396+
}
397+
398+
// checkSixelSupport detects if the terminal supports sixel graphics
399+
func checkSixelSupport() bool {
400+
// Check TERM environment variable for known sixel-capable terminals
401+
term := os.Getenv("TERM")
402+
sixelTerms := []string{"xterm-256color", "mlterm", "yaft-256color", "foot", "contour", "tmux-256color"}
403+
404+
for _, st := range sixelTerms {
405+
if strings.Contains(term, st) {
406+
return true
407+
}
408+
}
409+
410+
// Check for explicit sixel support environment variable
411+
if os.Getenv("SIXEL_SUPPORT") == "1" {
412+
return true
413+
}
414+
return false
415+
}

0 commit comments

Comments
 (0)