Files
lazyrun/internal/icon/install.go
T
oxpoe 59145910b5 Ikon: cari per domain/URL gambar + semua format dikonversi ke PNG
- Dialog ikon: nama aplikasi -> Iconify; domain (mikrotik.com) -> ikon resmi situs
  (manifest 512, apple-touch-icon, favicon) + layanan favicon; URL gambar -> langsung
- Contoh kasus: 'winbox' cuma punya 1 ikon tidak nyambung di Iconify, tapi
  'mikrotik.com' memberi ikon resminya (5 pilihan, semuanya PNG)
- Decoder ICO: entri BMP 24/32-bit (favicon.ico lama) ikut didukung, bukan cuma PNG
- FromURL/Fetch selalu mengembalikan PNG; hasil raster/konversi seragam 512px
- Tes: deteksi URL gambar/domain, daftar ikon situs, decode ICO BMP, konversi format
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 atau gambar BMP di dalamnya
if img, err := decodeICO(data); err == nil {
return encodePNG(img)
}
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
}