Files
lazyrun/internal/shortcut/desktop.go
T
oxpoe da31f7d7d7 LazyRun v1.0.0: shortcut maker untuk executable dan PWA
- Fungsi A: shortcut dari file executable (ELF/AppImage/script)
- Fungsi B: shortcut PWA/URL dengan deteksi browser (native, /opt, Flatpak)
- Checkbox Menu Aplikasi / Desktop, desktop-only pakai NoDisplay=true
- List + uninstall + deteksi shortcut yang file .desktop-nya hilang
- Metadata ~/.config/lazyrun/shortcuts.json, refresh cache XDG/KDE
- AppImage + zsync update info via build.sh
2026-10-13 08:00:12 +07:00

90 lines
2.4 KiB
Go

package shortcut
import (
"fmt"
"os"
"path/filepath"
"strings"
"unicode"
)
// Entry adalah isi file .desktop yang dihasilkan LazyRun.
type Entry struct {
Name string
ExecArgs []string // argv lengkap; tiap arg di-quote sesuai spec Exec
Icon string // path absolut atau nama ikon
Categories string // sudah diakhiri ";" atau kosong
// NoDisplay menyembunyikan entry dari menu aplikasi. Dipakai kalau user
// hanya memilih Desktop: file tetap ada di applications/ sebagai sumber kebenaran.
NoDisplay bool
}
// Render menghasilkan isi file .desktop.
func (e Entry) Render() string {
var b strings.Builder
b.WriteString("[Desktop Entry]\n")
b.WriteString("Type=Application\n")
fmt.Fprintf(&b, "Name=%s\n", escapeValue(e.Name))
fmt.Fprintf(&b, "Exec=%s\n", execArgs(e.ExecArgs))
if e.Icon != "" {
fmt.Fprintf(&b, "Icon=%s\n", escapeValue(e.Icon))
}
b.WriteString("Terminal=false\n")
if e.Categories != "" {
fmt.Fprintf(&b, "Categories=%s\n", escapeValue(e.Categories))
}
if e.NoDisplay {
b.WriteString("NoDisplay=true\n")
}
b.WriteString("Comment=Created by LazyRun\n")
return b.String()
}
// WriteDesktop menulis entry .desktop ke path.
func WriteDesktop(path string, e Entry) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
return os.WriteFile(path, []byte(e.Render()), 0o644)
}
func execArgs(args []string) string {
quoted := make([]string, len(args))
for i, a := range args {
quoted[i] = execQuote(a)
}
return strings.Join(quoted, " ")
}
// execQuote mengutip satu argumen sesuai aturan field Exec (spec Desktop Entry).
func execQuote(s string) string {
if s != "" && !strings.ContainsAny(s, " \t\n\"'\\><~|&;$*?#()`") {
return s
}
r := strings.NewReplacer("\\", "\\\\", "\"", "\\\"", "`", "\\`", "$", "\\$", "\n", "\\n")
return "\"" + r.Replace(s) + "\""
}
// escapeValue meng-escape nilai string biasa (Name, Icon, Categories).
func escapeValue(s string) string {
r := strings.NewReplacer("\\", "\\\\", "\n", "\\n", "\r", "\\r", "\t", "\\t")
return r.Replace(s)
}
// slugify membuat nama file yang aman dari nama aplikasi.
func slugify(name string) string {
var b strings.Builder
dash := false
for _, r := range strings.ToLower(name) {
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', unicode.IsLetter(r), unicode.IsDigit(r):
b.WriteRune(r)
dash = false
case b.Len() > 0 && !dash:
b.WriteByte('-')
dash = true
}
}
return strings.Trim(b.String(), "-")
}