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,89 @@
|
||||
// Package cursor meneruskan cursor theme desktop (KDE/GNOME) ke Fyne/GLFW.
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Apply men-set XCURSOR_THEME dan XCURSOR_SIZE kalau sesi belum men-set-nya.
|
||||
// Fyne (GLFW) membaca env ini waktu memuat cursor, jadi panggil sebelum app dibuat.
|
||||
func Apply() {
|
||||
if os.Getenv("XCURSOR_SIZE") == "" {
|
||||
if _, size := desktopCursor(); size != "" {
|
||||
os.Setenv("XCURSOR_SIZE", size)
|
||||
}
|
||||
}
|
||||
if os.Getenv("XCURSOR_THEME") != "" {
|
||||
return
|
||||
}
|
||||
if theme, _ := desktopCursor(); theme != "" {
|
||||
os.Setenv("XCURSOR_THEME", theme)
|
||||
}
|
||||
}
|
||||
|
||||
func home() string {
|
||||
h, _ := os.UserHomeDir()
|
||||
return h
|
||||
}
|
||||
|
||||
func configHome() string {
|
||||
if v := os.Getenv("XDG_CONFIG_HOME"); v != "" {
|
||||
return v
|
||||
}
|
||||
return filepath.Join(home(), ".config")
|
||||
}
|
||||
|
||||
// desktopCursor mengambil nama & ukuran cursor dari config KDE, GTK, atau ~/.icons/default.
|
||||
func desktopCursor() (theme, size string) {
|
||||
// KDE Plasma: ~/.config/kcminputrc
|
||||
kde := ini(filepath.Join(configHome(), "kcminputrc"))
|
||||
if t := kde["mouse"]["cursortheme"]; t != "" {
|
||||
return t, kde["mouse"]["cursorsize"]
|
||||
}
|
||||
|
||||
for _, gtk := range []string{"gtk-4.0", "gtk-3.0"} {
|
||||
cfg := ini(filepath.Join(configHome(), gtk, "settings.ini"))
|
||||
if t := cfg["settings"]["gtk-cursor-theme-name"]; t != "" {
|
||||
return t, cfg["settings"]["gtk-cursor-theme-size"]
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: theme default di ~/.icons/default/index.theme (format "Inherits=")
|
||||
fallback := filepath.Join(home(), ".icons", "default", "index.theme")
|
||||
if t := ini(fallback)["icon theme"]["inherits"]; t != "" {
|
||||
return t, ""
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// ini membaca file config berformat INI sederhana; key disimpan huruf kecil.
|
||||
func ini(path string) map[string]map[string]string {
|
||||
out := map[string]map[string]string{}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
|
||||
section := ""
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
section = strings.ToLower(strings.Trim(line, "[]"))
|
||||
continue
|
||||
}
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if out[section] == nil {
|
||||
out[section] = map[string]string{}
|
||||
}
|
||||
out[section][strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIniParsesSections(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "index.theme")
|
||||
body := "# komentar\n[Icon Theme]\nName=default\nInherits=Breeze_Cursors\n\n[Other]\nX=1\n"
|
||||
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := ini(path)
|
||||
if got["icon theme"]["inherits"] != "Breeze_Cursors" {
|
||||
t.Errorf("Inherits salah: %q", got["icon theme"]["inherits"])
|
||||
}
|
||||
if got["other"]["x"] != "1" {
|
||||
t.Errorf("section lain salah: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesktopCursorFromKDE(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||
|
||||
dir := filepath.Join(home, ".config")
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "kcminputrc"), []byte("[Mouse]\ncursorTheme=material_cursors\ncursorSize=36\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
theme, size := desktopCursor()
|
||||
if theme != "material_cursors" || size != "36" {
|
||||
t.Errorf("desktopCursor() = %q, %q", theme, size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplySetsEnvWhenEmpty(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||
t.Setenv("XCURSOR_THEME", "")
|
||||
t.Setenv("XCURSOR_SIZE", "")
|
||||
|
||||
dir := filepath.Join(home, ".config")
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "kcminputrc"), []byte("[Mouse]\ncursorTheme=material_cursors\ncursorSize=36\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Apply()
|
||||
if got := os.Getenv("XCURSOR_THEME"); got != "material_cursors" {
|
||||
t.Errorf("XCURSOR_THEME = %q", got)
|
||||
}
|
||||
if got := os.Getenv("XCURSOR_SIZE"); got != "36" {
|
||||
t.Errorf("XCURSOR_SIZE = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyKeepsExistingEnv(t *testing.T) {
|
||||
t.Setenv("XCURSOR_THEME", "Breeze_Cursors")
|
||||
t.Setenv("XCURSOR_SIZE", "24")
|
||||
|
||||
Apply()
|
||||
if got := os.Getenv("XCURSOR_THEME"); got != "Breeze_Cursors" {
|
||||
t.Errorf("env yang sudah ada tidak boleh ditimpa, dapat %q", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user