Files
lazyrun/internal/icon/install.go
T
oxpoe cad41caaee Hapus tombol kosong bawaan Fyne di modal + semua format ikon dikonversi ke PNG
- dialog.NewCustom selalu menambah tombol dismiss walau labelnya kosong;
  modal kita pakai baris tombol sendiri, jadi pindah ke NewCustomWithoutButtons
  (ditambah tes yang menolak pemakaian dialog.NewCustom lagi)
- Ikon: SVG/webp/gif/bmp/ico/jpeg semuanya dikonversi ke PNG saat dipasang
  (desktop environment tidak konsisten membaca format lain), gambar besar
  diperkecil ke maksimal 512px
- Filter file picker ikon menambah webp/gif/bmp
- Perbaiki data race pada endpoint pencarian ikon (iconifyBase) yang ditemukan
  oleh go test -race; tes UI menunggu goroutine pencarian selesai
- go test -race -count=1 ./... hijau untuk semua paket
2026-10-13 08:00:12 +07:00

113 lines
2.9 KiB
Go

// Package icon menyimpan ikon shortcut ke direktori ikon XDG.
package icon
import (
"bytes"
"errors"
"image"
"image/png"
"os"
"path/filepath"
"strings"
xdraw "golang.org/x/image/draw"
"lazyrun/assets"
"lazyrun/internal/xdg"
)
var exts = []string{".png", ".svg", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".ico", ".xpm"}
var pngMagic = []byte("\x89PNG\r\n\x1a\n")
// maxSide adalah sisi terpanjang ikon yang disimpan.
const maxSide = 512
// Default memasang ikon bawaan LazyRun.
func Default(slug string) (string, error) {
return Save(assets.Icon, slug)
}
// Nearby mencari file ikon bernama base.* di folder dir (dipakai untuk auto-deteksi).
func Nearby(dir, base string) string {
if dir == "" || base == "" {
return ""
}
return findNearby(dir, base)
}
func findNearby(dir, base string) string {
base = strings.ToLower(base)
for _, ext := range exts {
p := filepath.Join(dir, base+ext)
if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p
}
}
return ""
}
// Save menulis ikon ke direktori ikon XDG sebagai PNG.
//
// Semua format (SVG, webp, gif, bmp, ico, jpeg) dikonversi dulu ke PNG karena
// desktop environment tidak konsisten membacanya, dan sebagian SVG dari internet
// punya fitur yang bikin proses menggambar berhenti di UI.
func Save(data []byte, slug string) (string, error) {
converted, err := ToPNG(data)
if err != nil {
return "", err
}
if err := os.MkdirAll(xdg.IconsDir(), 0o755); err != nil {
return "", err
}
dest := filepath.Join(xdg.IconsDir(), slug+".png")
return dest, os.WriteFile(dest, converted, 0o644)
}
// Ext menebak ekstensi file dari isi data.
func Ext(data []byte) string { return sniffExt(data) }
// IconBytes adalah ikon default LazyRun.
func IconBytes() []byte { return assets.Icon }
// ToPNG mengubah data gambar apa pun yang didukung menjadi PNG.
// Data yang sudah PNG dikembalikan apa adanya.
func ToPNG(data []byte) ([]byte, error) {
if bytes.HasPrefix(data, pngMagic) {
return data, nil
}
if sniffExt(data) == ".svg" {
return Rasterize(data, 256)
}
// jpeg, gif, bmp, webp dikenali dari isi file
if img, _, err := image.Decode(bytes.NewReader(data)); err == nil {
return encodePNG(img)
}
// ico bisa berisi PNG langsung
if png := largestPNGFromICO(data); png != nil {
return png, nil
}
return nil, errors.New("format ikon tidak didukung")
}
// encodePNG menulis gambar jadi PNG, diperkecil kalau lebih besar dari maxSide.
func encodePNG(img image.Image) ([]byte, error) {
bounds := img.Bounds()
if bounds.Dx() > maxSide || bounds.Dy() > maxSide {
scale := float64(maxSide) / float64(max(bounds.Dx(), bounds.Dy()))
size := image.Rect(0, 0, int(float64(bounds.Dx())*scale), int(float64(bounds.Dy())*scale))
scaled := image.NewNRGBA(size)
xdraw.CatmullRom.Scale(scaled, size, img, bounds, xdraw.Over, nil)
img = scaled
}
var out bytes.Buffer
if err := png.Encode(&out, img); err != nil {
return nil, err
}
return out.Bytes(), nil
}