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
This commit is contained in:
1 parent
9cd5a1f82d
commit
da31f7d7d7
31 files changed
+2916
No files matched your search
@@ -0,0 +1,131 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/internal/shortcut"
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
|
||||
var categories = []string{
|
||||
"Utility", "Network", "Development", "Graphics",
|
||||
"AudioVideo", "Office", "Game", "Education", "System", "Settings",
|
||||
}
|
||||
|
||||
var (
|
||||
imageFilter = storage.NewExtensionFileFilter([]string{".png", ".svg", ".jpg", ".jpeg", ".xpm", ".ico"})
|
||||
stripExts = []string{".AppImage", ".appimage", ".sh", ".bin", ".run", ".desktop"}
|
||||
)
|
||||
|
||||
func (ui *app) showExecutableDialog() {
|
||||
var binPath, iconPath string
|
||||
|
||||
binLabel := widget.NewLabel(T("none"))
|
||||
iconLabel := widget.NewLabel(T("icon_auto"))
|
||||
nameEntry := widget.NewEntry()
|
||||
category := widget.NewSelect(categories, nil)
|
||||
category.SetSelected("Utility")
|
||||
menu := widget.NewCheck(T("check_menu"), nil)
|
||||
menu.SetChecked(true)
|
||||
desktop := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktop.SetChecked(true)
|
||||
|
||||
browseBin := widget.NewButton(T("browse"), func() {
|
||||
pickFile(ui.win, nil, func(path string) {
|
||||
binPath = path
|
||||
binLabel.SetText(filepath.Base(path))
|
||||
if strings.TrimSpace(nameEntry.Text) == "" {
|
||||
nameEntry.SetText(appNameFromFile(path))
|
||||
}
|
||||
})
|
||||
})
|
||||
browseIcon := widget.NewButton(T("browse"), func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath = path
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
})
|
||||
})
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
install := widget.NewButton(T("install"), func() {
|
||||
switch {
|
||||
case binPath == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_binary"), ui.win)
|
||||
return
|
||||
case strings.TrimSpace(nameEntry.Text) == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_name"), ui.win)
|
||||
return
|
||||
case !menu.Checked && !desktop.Checked:
|
||||
dialog.ShowInformation(T("err_title"), T("need_target"), ui.win)
|
||||
return
|
||||
}
|
||||
opts := shortcut.ExecOptions{
|
||||
BinaryPath: binPath,
|
||||
IconPath: iconPath,
|
||||
Name: nameEntry.Text,
|
||||
Category: category.Selected,
|
||||
Menu: menu.Checked,
|
||||
Desktop: desktop.Checked,
|
||||
}
|
||||
ui.install(
|
||||
func() (store.Shortcut, error) { return shortcut.InstallExecutable(opts) },
|
||||
func(sc store.Shortcut, err error) { ui.added(sc, err, d.Hide) },
|
||||
)
|
||||
})
|
||||
install.Importance = widget.HighImportance
|
||||
|
||||
form := widget.NewForm(
|
||||
widget.NewFormItem(T("field_binary"), container.NewBorder(nil, nil, nil, browseBin, binLabel)),
|
||||
widget.NewFormItem(T("field_icon"), container.NewBorder(nil, nil, nil, browseIcon, iconLabel)),
|
||||
widget.NewFormItem(T("field_name"), nameEntry),
|
||||
widget.NewFormItem(T("field_category"), category),
|
||||
)
|
||||
content := container.NewVBox(
|
||||
form,
|
||||
widget.NewSeparator(),
|
||||
heading(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
container.NewHBox(spacer(), widget.NewButton(T("cancel"), func() { d.Hide() }), install),
|
||||
)
|
||||
|
||||
d = dialog.NewCustom(T("new_exec_title"), "", content, ui.win)
|
||||
d.Resize(fyne.NewSize(520, 460))
|
||||
d.Show()
|
||||
}
|
||||
|
||||
// pickFile membuka file picker dan mengirim path absolut ke onPick.
|
||||
func pickFile(win fyne.Window, filter storage.FileFilter, onPick func(path string)) {
|
||||
fd := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
onPick(r.URI().Path())
|
||||
}, win)
|
||||
if filter != nil {
|
||||
fd.SetFilter(filter)
|
||||
}
|
||||
fd.Show()
|
||||
}
|
||||
|
||||
func appNameFromFile(path string) string {
|
||||
base := filepath.Base(path)
|
||||
for _, ext := range stripExts {
|
||||
if strings.HasSuffix(base, ext) {
|
||||
return strings.TrimSuffix(base, ext)
|
||||
}
|
||||
}
|
||||
return base
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/internal/browser"
|
||||
"lazyrun/internal/shortcut"
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
|
||||
func (ui *app) showWebAppDialog() {
|
||||
if len(ui.browsers) == 0 {
|
||||
dialog.ShowInformation(T("err_title"), T("no_browser"), ui.win)
|
||||
return
|
||||
}
|
||||
|
||||
var iconPath string
|
||||
var nameTouched, filling bool
|
||||
|
||||
urlEntry := widget.NewEntry()
|
||||
urlEntry.SetPlaceHolder("https://web.whatsapp.com")
|
||||
nameEntry := widget.NewEntry()
|
||||
iconLabel := widget.NewLabel(T("icon_auto"))
|
||||
picker := widget.NewSelect(browserNames(ui.browsers), nil)
|
||||
picker.SetSelected(ui.browsers[0].Name)
|
||||
menu := widget.NewCheck(T("check_menu"), nil)
|
||||
menu.SetChecked(true)
|
||||
desktop := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktop.SetChecked(true)
|
||||
|
||||
nameEntry.OnChanged = func(string) {
|
||||
if !filling {
|
||||
nameTouched = true
|
||||
}
|
||||
}
|
||||
urlEntry.OnChanged = func(value string) {
|
||||
if nameTouched {
|
||||
return
|
||||
}
|
||||
if host := hostOf(value); host != "" {
|
||||
filling = true
|
||||
nameEntry.SetText(host)
|
||||
filling = false
|
||||
}
|
||||
}
|
||||
|
||||
browseIcon := widget.NewButton(T("browse"), func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath = path
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
})
|
||||
})
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
install := widget.NewButton(T("install"), func() {
|
||||
switch {
|
||||
case strings.TrimSpace(urlEntry.Text) == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_url"), ui.win)
|
||||
return
|
||||
case strings.TrimSpace(nameEntry.Text) == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_name"), ui.win)
|
||||
return
|
||||
case !menu.Checked && !desktop.Checked:
|
||||
dialog.ShowInformation(T("err_title"), T("need_target"), ui.win)
|
||||
return
|
||||
}
|
||||
b := ui.browsers[indexOf(ui.browsers, picker.Selected)]
|
||||
opts := shortcut.WebOptions{
|
||||
URL: urlEntry.Text,
|
||||
Name: nameEntry.Text,
|
||||
Browser: shortcut.BrowserArg{Name: b.Name, Exec: b.Exec, Firefox: b.Kind == browser.Firefox},
|
||||
IconPath: iconPath,
|
||||
Menu: menu.Checked,
|
||||
Desktop: desktop.Checked,
|
||||
}
|
||||
ui.install(
|
||||
func() (store.Shortcut, error) { return shortcut.InstallWebApp(opts) },
|
||||
func(sc store.Shortcut, err error) { ui.added(sc, err, d.Hide) },
|
||||
)
|
||||
})
|
||||
install.Importance = widget.HighImportance
|
||||
|
||||
form := widget.NewForm(
|
||||
widget.NewFormItem(T("field_url"), urlEntry),
|
||||
widget.NewFormItem(T("field_name"), nameEntry),
|
||||
widget.NewFormItem(T("field_browser"), picker),
|
||||
widget.NewFormItem(T("field_icon"), container.NewBorder(nil, nil, nil, browseIcon, iconLabel)),
|
||||
)
|
||||
content := container.NewVBox(
|
||||
form,
|
||||
widget.NewSeparator(),
|
||||
heading(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
container.NewHBox(spacer(), widget.NewButton(T("cancel"), func() { d.Hide() }), install),
|
||||
)
|
||||
|
||||
d = dialog.NewCustom(T("new_web_title"), "", content, ui.win)
|
||||
d.Resize(fyne.NewSize(520, 440))
|
||||
d.Show()
|
||||
}
|
||||
|
||||
func browserNames(list []browser.Browser) []string {
|
||||
names := make([]string, len(list))
|
||||
for i, b := range list {
|
||||
names[i] = b.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func indexOf(list []browser.Browser, name string) int {
|
||||
for i, b := range list {
|
||||
if b.Name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// hostOf mengambil hostname dari URL yang mungkin ditulis tanpa skema.
|
||||
func hostOf(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.Contains(raw, "://") {
|
||||
raw = "https://" + raw
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return u.Hostname()
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Package ui berisi window utama dan dialog LazyRun.
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/assets"
|
||||
"lazyrun/internal/browser"
|
||||
"lazyrun/internal/shortcut"
|
||||
"lazyrun/internal/store"
|
||||
"lazyrun/internal/xdg"
|
||||
)
|
||||
|
||||
type app struct {
|
||||
win fyne.Window
|
||||
store *store.Store
|
||||
browsers []browser.Browser
|
||||
rows *fyne.Container
|
||||
version string
|
||||
}
|
||||
|
||||
// Run menampilkan window utama LazyRun sampai user menutupnya.
|
||||
func Run(a fyne.App, version string) {
|
||||
ui, loadErr := build(a, version)
|
||||
ui.win.Show()
|
||||
if loadErr != nil {
|
||||
dialog.ShowError(loadErr, ui.win)
|
||||
}
|
||||
a.Run()
|
||||
}
|
||||
|
||||
// build menyiapkan window utama tanpa menampilkannya (dipakai Run dan tes headless).
|
||||
func build(a fyne.App, version string) (*app, error) {
|
||||
ui := &app{version: version}
|
||||
a.SetIcon(fyne.NewStaticResource("lazyrun.png", assets.Icon))
|
||||
|
||||
ui.browsers = browser.Detect()
|
||||
ui.rows = container.NewVBox()
|
||||
ui.win = a.NewWindow(T("app_title"))
|
||||
ui.win.Resize(fyne.NewSize(760, 540))
|
||||
|
||||
top := container.NewHBox(
|
||||
widget.NewButton(T("btn_executable"), ui.showExecutableDialog),
|
||||
widget.NewButton(T("btn_webapp"), ui.showWebAppDialog),
|
||||
)
|
||||
bottom := container.NewBorder(nil, nil,
|
||||
widget.NewButton(T("check_updates"), ui.checkUpdates),
|
||||
widget.NewLabel("v"+version))
|
||||
body := container.NewBorder(heading(T("installed")), nil, nil, nil, container.NewVScroll(ui.rows))
|
||||
ui.win.SetContent(container.NewBorder(top, bottom, nil, nil, body))
|
||||
|
||||
st, err := store.Load(xdg.MetadataPath())
|
||||
ui.store = st
|
||||
ui.reload()
|
||||
return ui, err
|
||||
}
|
||||
|
||||
func (ui *app) reload() {
|
||||
if st, err := store.Load(ui.store.Path); err == nil {
|
||||
ui.store = st
|
||||
}
|
||||
ui.rows.RemoveAll()
|
||||
if len(ui.store.Items) == 0 {
|
||||
ui.rows.Add(widget.NewLabel(T("empty")))
|
||||
}
|
||||
for _, sc := range ui.store.Items {
|
||||
ui.rows.Add(ui.row(sc))
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *app) row(sc store.Shortcut) fyne.CanvasObject {
|
||||
title := widget.NewLabelWithStyle(sc.Name, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
info := widget.NewLabel(describe(sc))
|
||||
|
||||
open := widget.NewButton(T("folder"), func() {
|
||||
if err := exec.Command("xdg-open", filepath.Dir(sc.DesktopPath)).Start(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
})
|
||||
remove := widget.NewButton(T("delete"), func() { ui.confirmDelete(sc) })
|
||||
|
||||
return container.NewBorder(nil, widget.NewSeparator(), nil,
|
||||
container.NewHBox(open, remove),
|
||||
container.NewVBox(title, info))
|
||||
}
|
||||
|
||||
func describe(sc store.Shortcut) string {
|
||||
targets := make([]string, 0, 2)
|
||||
if sc.Targets.Menu {
|
||||
targets = append(targets, T("check_menu"))
|
||||
}
|
||||
if sc.Targets.Desktop {
|
||||
targets = append(targets, T("check_desktop"))
|
||||
}
|
||||
loc := strings.Join(targets, " + ")
|
||||
|
||||
kind := "Executable"
|
||||
if sc.Type == store.TypeWebApp {
|
||||
kind = "Web"
|
||||
}
|
||||
line := kind + " · " + loc
|
||||
if sc.Broken() {
|
||||
line += " · " + T("broken")
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
func (ui *app) confirmDelete(sc store.Shortcut) {
|
||||
content := container.NewVBox(widget.NewLabel(fmt.Sprintf(T("confirm_delete_msg"), sc.Name)))
|
||||
|
||||
var removeBinary *widget.Check
|
||||
if sc.Type == store.TypeExecutable && sc.BinaryPath != "" {
|
||||
removeBinary = widget.NewCheck(T("also_binary"), nil)
|
||||
content.Add(removeBinary)
|
||||
}
|
||||
|
||||
d := dialog.NewCustomConfirm(T("confirm_delete_title"), T("delete"), T("cancel"), content, func(ok bool) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
opts := shortcut.RemoveOptions{}
|
||||
if removeBinary != nil {
|
||||
opts.Binary = removeBinary.Checked
|
||||
}
|
||||
if err := shortcut.Uninstall(sc, opts); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
ui.store.Remove(sc.Slug)
|
||||
if err := ui.store.Save(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
ui.reload()
|
||||
}, ui.win)
|
||||
d.Show()
|
||||
}
|
||||
|
||||
// install menjalankan pekerjaan berat (salin file besar / fetch favicon) di luar UI thread.
|
||||
func (ui *app) install(work func() (store.Shortcut, error), done func(store.Shortcut, error)) {
|
||||
progress := dialog.NewProgressInfinite(T("install"), T("working"), ui.win)
|
||||
progress.Show()
|
||||
go func() {
|
||||
sc, err := work()
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
done(sc, err)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// added menyimpan metadata shortcut baru; dipanggil dari callback install.
|
||||
func (ui *app) added(sc store.Shortcut, err error, closeDialog func()) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
ui.store.Add(sc)
|
||||
if err := ui.store.Save(); err != nil {
|
||||
ui.store.Remove(sc.Slug)
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
closeDialog()
|
||||
ui.reload()
|
||||
dialog.ShowInformation(T("success_title"), fmt.Sprintf(T("success_msg"), sc.Name), ui.win)
|
||||
}
|
||||
|
||||
func heading(text string) *widget.Label {
|
||||
return widget.NewLabelWithStyle(text, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
}
|
||||
|
||||
func spacer() fyne.CanvasObject { return layout.NewSpacer() }
|
||||
@@ -0,0 +1,35 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fyne.io/fyne/v2/test"
|
||||
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
|
||||
func TestBuildMainWindow(t *testing.T) {
|
||||
a := test.NewApp()
|
||||
defer a.Quit()
|
||||
|
||||
ui, err := build(a, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("build(): %v", err)
|
||||
}
|
||||
if ui.win.Content() == nil {
|
||||
t.Fatal("window tanpa content")
|
||||
}
|
||||
|
||||
// dialog harus bisa dibuka tanpa panic
|
||||
ui.showExecutableDialog()
|
||||
ui.showWebAppDialog()
|
||||
ui.checkUpdates()
|
||||
}
|
||||
|
||||
func TestDescribe(t *testing.T) {
|
||||
sc := store.Shortcut{Type: store.TypeWebApp, Targets: store.Targets{Menu: true}, DesktopPath: "/nonexistent/x.desktop"}
|
||||
got := describe(sc)
|
||||
if got != "Web · Application menu · missing" && got != "Web · Menu aplikasi · hilang" {
|
||||
t.Errorf("describe() = %q", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tr menyimpan teks UI: index 0 = Inggris, 1 = Indonesia.
|
||||
var tr = map[string][2]string{
|
||||
"app_title": {"LazyRun — lazy shortcut build", "LazyRun — lazy shortcut build"},
|
||||
"btn_executable": {"+ Executable", "+ Executable"},
|
||||
"btn_webapp": {"+ Web App", "+ Aplikasi Web"},
|
||||
"installed": {"Installed shortcuts", "Shortcut terinstall"},
|
||||
"empty": {"No shortcuts yet. Add one with the buttons above.", "Belum ada shortcut. Tambahkan lewat tombol di atas."},
|
||||
"none": {"not selected", "belum dipilih"},
|
||||
"icon_auto": {"automatic", "otomatis"},
|
||||
"browse": {"Browse...", "Pilih..."},
|
||||
"install": {"Install", "Install"},
|
||||
"cancel": {"Cancel", "Batal"},
|
||||
"delete": {"Delete", "Hapus"},
|
||||
"folder": {"Folder", "Folder"},
|
||||
"working": {"Working...", "Sedang diproses..."},
|
||||
|
||||
"new_exec_title": {"New Executable Shortcut", "Shortcut Executable Baru"},
|
||||
"new_web_title": {"New Web App Shortcut", "Shortcut Web App Baru"},
|
||||
"field_binary": {"Binary", "Binary"},
|
||||
"field_icon": {"Icon (optional)", "Ikon (opsional)"},
|
||||
"field_name": {"Name", "Nama"},
|
||||
"field_category": {"Category", "Kategori"},
|
||||
"field_url": {"URL", "URL"},
|
||||
"field_browser": {"Browser", "Browser"},
|
||||
|
||||
"section_location": {"Shortcut location", "Lokasi shortcut"},
|
||||
"check_menu": {"Application menu", "Menu aplikasi"},
|
||||
"check_desktop": {"Desktop", "Desktop"},
|
||||
|
||||
"confirm_delete_title": {"Uninstall shortcut", "Hapus shortcut"},
|
||||
"confirm_delete_msg": {"Remove %s?", "Hapus %s?"},
|
||||
"also_binary": {"Also delete the copied binary", "Hapus binary hasil salinan juga"},
|
||||
|
||||
"success_title": {"Done", "Selesai"},
|
||||
"success_msg": {"%s is ready.", "%s sudah siap."},
|
||||
"err_title": {"Cannot continue", "Tidak bisa lanjut"},
|
||||
|
||||
"need_binary": {"Choose a binary first.", "Pilih binary dulu."},
|
||||
"need_name": {"Enter a name first.", "Masukkan nama dulu."},
|
||||
"need_url": {"Enter a URL first.", "Masukkan URL dulu."},
|
||||
"need_target": {"Pick at least one location: menu or desktop.", "Pilih minimal satu lokasi: menu atau desktop."},
|
||||
"no_browser": {"No supported browser found. Install Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox first.", "Browser yang didukung tidak ditemukan. Install Chrome, Chromium, Brave, Edge, Vivaldi, atau Firefox dulu."},
|
||||
"broken": {"missing", "hilang"},
|
||||
|
||||
"check_updates": {"Check for Updates", "Cek Update"},
|
||||
"not_appimage": {"This build is not an AppImage (version %s). Update from where you got it.", "Build ini bukan AppImage (versi %s). Update dari sumber kamu mendapatkannya."},
|
||||
"no_appimageupdate": {"AppImageUpdate is not installed. Install it to update LazyRun automatically.", "AppImageUpdate tidak terpasang. Install dulu supaya LazyRun bisa update otomatis."},
|
||||
}
|
||||
|
||||
var lang = detectLang()
|
||||
|
||||
// T mengembalikan teks UI sesuai $LANG: "id*" → Indonesia, selain itu Inggris.
|
||||
func T(key string) string {
|
||||
s, ok := tr[key]
|
||||
if !ok {
|
||||
return key
|
||||
}
|
||||
if lang == "id" {
|
||||
return s[1]
|
||||
}
|
||||
return s[0]
|
||||
}
|
||||
|
||||
func detectLang() string {
|
||||
v := os.Getenv("LC_ALL")
|
||||
if v == "" {
|
||||
v = os.Getenv("LC_MESSAGES")
|
||||
}
|
||||
if v == "" {
|
||||
v = os.Getenv("LANG")
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(v), "id") {
|
||||
return "id"
|
||||
}
|
||||
return "en"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
)
|
||||
|
||||
// checkUpdates menjalankan AppImageUpdate kalau LazyRun berjalan sebagai AppImage.
|
||||
func (ui *app) checkUpdates() {
|
||||
image := os.Getenv("APPIMAGE")
|
||||
if image == "" {
|
||||
dialog.ShowInformation(T("check_updates"), fmt.Sprintf(T("not_appimage"), ui.version), ui.win)
|
||||
return
|
||||
}
|
||||
update, err := exec.LookPath("AppImageUpdate")
|
||||
if err != nil {
|
||||
dialog.ShowInformation(T("check_updates"), T("no_appimageupdate"), ui.win)
|
||||
return
|
||||
}
|
||||
if err := exec.Command(update, image).Start(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user