Ikon otomatis dari internet + cursor mengikuti tema host
- Cari ikon online (Iconify) dari dialog install, plus tombol sync ikon per shortcut - Auto ikon: file terdekat -> favicon situs (termasuk ikon manifest PWA) -> ikon brand yang namanya cocok -> ikon default; sumber ikon disimpan di metadata - Query dibersihkan (web.whatsapp.com -> whatsapp) dan kata <4 huruf tidak dipakai supaya aplikasi tanpa ikon resmi tidak dapat ikon acak - Unduhan thumbnail dibatasi 4 bersamaan + retry sekali (rate limit Iconify) - Cursor: XCURSOR_THEME/XCURSOR_SIZE diambil dari config KDE/GTK/~/.icons default, dan AppImage tidak lagi membundel libX*/libwayland*/libxkbcommon (dipakai dari host) - README: bagian ikon + integrasi desktop
This commit is contained in:
1 parent
70534bf6e2
commit
7f7cb97a65
19 files changed
+1117
-40
No files matched your search
@@ -0,0 +1,194 @@
|
||||
package shortcut
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"lazyrun/internal/icon"
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
|
||||
// iconSetEndpoint mengarahkan pencarian ikon ke server tes.
|
||||
func iconSetEndpoint(t *testing.T, base string) {
|
||||
t.Helper()
|
||||
icon.SetSearchEndpoint(base)
|
||||
t.Cleanup(func() { icon.SetSearchEndpoint("http://127.0.0.1:1") })
|
||||
}
|
||||
|
||||
func TestRefreshIconFromURL(t *testing.T) {
|
||||
testEnv(t)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(append(pngMagic, []byte("ikon-baru")...))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
deskDst := filepath.Join(t.TempDir(), "x.desktop")
|
||||
old := Entry{Name: "X", ExecArgs: []string{"/x"}, Icon: "/lama/icon.png"}
|
||||
if err := WriteDesktop(deskDst, old); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sc := store.Shortcut{
|
||||
Slug: "x",
|
||||
Name: "X",
|
||||
IconPath: "/lama/icon.png",
|
||||
IconSource: srv.URL + "/icon.png",
|
||||
DesktopPath: deskDst,
|
||||
}
|
||||
path, err := RefreshIcon(sc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if filepath.Base(path) != "x.png" {
|
||||
t.Errorf("ikon baru di %s", path)
|
||||
}
|
||||
if data, err := os.ReadFile(path); err != nil || len(data) == 0 {
|
||||
t.Errorf("ikon tidak tertulis: %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(deskDst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(content), "Icon="+path+"\n") {
|
||||
t.Errorf("baris Icon= tidak diperbarui:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshIconKeepsDesktopCopyMode(t *testing.T) {
|
||||
home := testEnv(t)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(append(pngMagic, []byte("baru")...))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
deskDst := filepath.Join(home, ".local/share/applications/y.desktop")
|
||||
copyDst := filepath.Join(home, "Desktop", "y.desktop")
|
||||
for _, path := range []string{deskDst, copyDst} {
|
||||
if err := WriteDesktop(path, Entry{Name: "Y", ExecArgs: []string{"/y"}, Icon: "/lama.png"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.Chmod(copyDst, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sc := store.Shortcut{
|
||||
Slug: "y",
|
||||
IconSource: srv.URL,
|
||||
DesktopPath: deskDst,
|
||||
DesktopCopyPath: copyDst,
|
||||
}
|
||||
if _, err := RefreshIcon(sc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := os.Stat(copyDst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Mode().Perm()&0o111 == 0 {
|
||||
t.Errorf("salinan desktop kehilangan mode executable: %v", st.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshIconWithoutOnlineSource(t *testing.T) {
|
||||
testEnv(t)
|
||||
sc := store.Shortcut{Slug: "z", IconSource: "file:/tmp/icon.png", DesktopPath: "/tmp/z.desktop"}
|
||||
if _, err := RefreshIcon(sc); err == nil {
|
||||
t.Error("ikon dari file lokal harus error, bukan diunduh")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallExecutableSearchesIconOnline(t *testing.T) {
|
||||
home := testEnv(t)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/search"):
|
||||
_, _ = w.Write([]byte(`{"icons":["mdi:winbox"]}`))
|
||||
case r.URL.Path == "/mdi/winbox.svg":
|
||||
_, _ = w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"></svg>`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
iconSetEndpoint(t, srv.URL)
|
||||
|
||||
src := fakeBinary(t, home, "winbox")
|
||||
sc, err := InstallExecutable(ExecOptions{BinaryPath: src, Name: "Winbox", Menu: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sc.IconSource != "search:Winbox" {
|
||||
t.Errorf("icon_source = %q", sc.IconSource)
|
||||
}
|
||||
if filepath.Ext(sc.IconPath) != ".svg" {
|
||||
t.Errorf("ikon online harus tersimpan sebagai .svg, dapat %s", sc.IconPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallWebAppUsesIconURL(t *testing.T) {
|
||||
testEnv(t)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/pilih.svg":
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
_, _ = w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"></svg>`))
|
||||
case "/":
|
||||
_, _ = w.Write([]byte(`<html><head><link rel="icon" href="/situs.png"></head></html>`))
|
||||
default:
|
||||
_, _ = w.Write(append(pngMagic, []byte("favicon-situs")...))
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
sc, err := InstallWebApp(WebOptions{
|
||||
URL: "https://example.com",
|
||||
Name: "Example",
|
||||
Browser: BrowserArg{Name: "Chromium", Exec: []string{"/usr/bin/chromium"}},
|
||||
IconURL: srv.URL + "/pilih.svg",
|
||||
Menu: true,
|
||||
Desktop: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sc.IconSource != srv.URL+"/pilih.svg" {
|
||||
t.Errorf("ikon pilihan user harus dipakai, icon_source = %q", sc.IconSource)
|
||||
}
|
||||
if filepath.Ext(sc.IconPath) != ".svg" {
|
||||
t.Errorf("ikon = %s", sc.IconPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallWebAppUsesIconDataFromUI(t *testing.T) {
|
||||
testEnv(t)
|
||||
|
||||
svg := []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"></svg>`)
|
||||
sc, err := InstallWebApp(WebOptions{
|
||||
URL: "https://example.com",
|
||||
Name: "Example",
|
||||
Browser: BrowserArg{Name: "Chromium", Exec: []string{"/usr/bin/chromium"}},
|
||||
IconURL: "https://api.iconify.design/mdi/example.svg?height=256",
|
||||
IconData: svg, // sudah diunduh UI: tidak perlu request ulang
|
||||
Menu: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sc.IconSource != "https://api.iconify.design/mdi/example.svg?height=256" {
|
||||
t.Errorf("icon_source = %q", sc.IconSource)
|
||||
}
|
||||
data, err := os.ReadFile(sc.IconPath)
|
||||
if err != nil || string(data) != string(svg) {
|
||||
t.Errorf("ikon tersimpan salah: %v %d byte", err, len(data))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user