244 lines
5.9 KiB
Go
244 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
_ "embed"
|
|
"fmt"
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/layout"
|
|
"fyne.io/fyne/v2/storage"
|
|
"fyne.io/fyne/v2/widget"
|
|
"github.com/mat/besticon/ico"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
//go:embed permafrost.png
|
|
var defLogo []byte
|
|
|
|
func createTab(window fyne.Window) *fyne.Container {
|
|
// Create entry field for name of SSB
|
|
ssbName := widget.NewEntry()
|
|
ssbName.SetPlaceHolder("App Name")
|
|
|
|
// Create entry field for URL of SSB
|
|
ssbURL := widget.NewEntry()
|
|
ssbURL.SetPlaceHolder("App URL")
|
|
|
|
// Create dropdown menu for category in desktop file
|
|
category := widget.NewSelectEntry([]string{
|
|
"AudioVideo",
|
|
"Audio",
|
|
"Video",
|
|
"Development",
|
|
"Education",
|
|
"Game",
|
|
"Graphics",
|
|
"Network",
|
|
"Office",
|
|
"Settings",
|
|
"System",
|
|
"Utility",
|
|
})
|
|
category.PlaceHolder = "Category"
|
|
|
|
// Get default logo and decode as png
|
|
img, err := png.Decode(bytes.NewReader(defLogo))
|
|
if err != nil {
|
|
errDisp(true, err, "Error decoding default logo", window)
|
|
}
|
|
// Get canvas image from png
|
|
defaultIcon := canvas.NewImageFromImage(img)
|
|
// Create new container for icon with placeholder line
|
|
iconContainer := container.NewMax(canvas.NewLine(color.Black))
|
|
// Set default icon in container
|
|
setIcon(defaultIcon, iconContainer)
|
|
|
|
// Create image selection dialog
|
|
selectImg := dialog.NewFileOpen(func(file fyne.URIReadCloser, err error) {
|
|
// Close file at end of function
|
|
defer file.Close()
|
|
if err != nil {
|
|
errDisp(true, err, "Error opening file", window)
|
|
}
|
|
// If no file selected, stop further execution of function
|
|
if file == nil {
|
|
return
|
|
}
|
|
// Get image from file reader
|
|
icon := canvas.NewImageFromReader(file, file.URI().Name())
|
|
setIcon(icon, iconContainer)
|
|
}, window)
|
|
// Create filter constrained to images
|
|
selectImg.SetFilter(storage.NewMimeTypeFileFilter([]string{"image/*"}))
|
|
|
|
// Create button to use favicon as icon
|
|
faviconBtn := widget.NewButton("Use favicon", func() {
|
|
// Attempt to parse URL
|
|
uri, err := url.ParseRequestURI(ssbURL.Text)
|
|
if err != nil {
|
|
errDisp(true, err, "Error parsing URL. Note that the scheme (https://) is required.", window)
|
|
return
|
|
}
|
|
// Attempt to get favicon using DuckDuckGo API
|
|
res, err := http.Get(fmt.Sprintf("https://external-content.duckduckgo.com/ip3/%s.ico", uri.Host))
|
|
if err != nil {
|
|
errDisp(true, err, "Error getting favicon via DuckDuckGo API", window)
|
|
return
|
|
}
|
|
defer res.Body.Close()
|
|
// Attempt to decode data as ico file
|
|
favicon, err := ico.Decode(res.Body)
|
|
if err != nil {
|
|
errDisp(true, err, "Error decoding ico file", window)
|
|
return
|
|
}
|
|
// Get new image from decoded data
|
|
icon := canvas.NewImageFromImage(favicon)
|
|
setIcon(icon, iconContainer)
|
|
})
|
|
|
|
// Create vertical container
|
|
col := container.NewVBox(
|
|
category,
|
|
widget.NewButton("Select icon", selectImg.Show),
|
|
faviconBtn,
|
|
)
|
|
|
|
// Use home directory to get icon path
|
|
iconDir := filepath.Join(home, ".config", name, "icons")
|
|
|
|
useChrome := widget.NewCheck("Use Chrome (not isolated)", nil)
|
|
|
|
// Create new button that creates SSB
|
|
createBtn := widget.NewButton("Create", func() {
|
|
// Attempt to create SSB
|
|
err := createSSB(ssbName.Text, ssbURL.Text, category.Text, useChrome.Checked, iconContainer.Objects[0].(*canvas.Image).Image)
|
|
if err != nil {
|
|
errDisp(true, err, "Error creating SSB", window)
|
|
}
|
|
refreshRmBtns(window, iconDir, rmBtns)
|
|
ssbName.SetText("")
|
|
ssbURL.SetText("")
|
|
category.SetText("")
|
|
useChrome.SetChecked(false)
|
|
setIcon(defaultIcon, iconContainer)
|
|
})
|
|
|
|
// Create new vertical container
|
|
content := container.New(layout.NewVBoxLayout(),
|
|
ssbName,
|
|
ssbURL,
|
|
// Create dual-column container to house icon and fields
|
|
container.NewGridWithColumns(2,
|
|
container.NewVBox(
|
|
container.NewCenter(widget.NewLabel("Icon")),
|
|
iconContainer,
|
|
useChrome,
|
|
),
|
|
col,
|
|
),
|
|
// Add expanding spacer
|
|
layout.NewSpacer(),
|
|
createBtn,
|
|
)
|
|
|
|
// Return base container
|
|
return content
|
|
}
|
|
|
|
func setIcon(img *canvas.Image, logoLayout *fyne.Container) {
|
|
// Expand image keeping aspect ratio
|
|
img.FillMode = canvas.ImageFillContain
|
|
// Set minimum image size to 50x50
|
|
img.SetMinSize(fyne.NewSize(50, 50))
|
|
// Replace image in layout
|
|
logoLayout.Objects[0] = img
|
|
// Refresh layout (update changes)
|
|
logoLayout.Refresh()
|
|
}
|
|
|
|
func createSSB(ssbName, uri, category string, useChrome bool, icon image.Image) error {
|
|
// Parse provided URL for validity
|
|
_, err := url.ParseRequestURI(uri)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Use home directory to get various paths
|
|
configDir := filepath.Join(home, ".config", name)
|
|
iconDir := filepath.Join(configDir, "icons", ssbName)
|
|
desktopDir := filepath.Join(home, ".local", "share", "applications")
|
|
|
|
// Create paths if nonexistent
|
|
err = makeDirs(configDir, iconDir, desktopDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get paths to resources
|
|
iconPath := filepath.Join(iconDir, "icon.png")
|
|
desktopPath := filepath.Join(desktopDir, ssbName+".desktop")
|
|
|
|
// Create icon file
|
|
iconFile, err := os.Create(iconPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Encode icon as png, writing to file
|
|
err = png.Encode(iconFile, icon)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
iconFile.Close()
|
|
|
|
if useChrome {
|
|
uri += " --chrome"
|
|
}
|
|
|
|
// Expand desktop file template with provided data
|
|
desktopStr := fmt.Sprintf(DesktopTemplate,
|
|
ssbName,
|
|
iconPath,
|
|
uri,
|
|
category,
|
|
)
|
|
|
|
// Create new executable desktop file
|
|
desktopFile, err := os.OpenFile(desktopPath, os.O_CREATE|os.O_RDWR, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Copy expanded desktop file template to file
|
|
_, err = io.Copy(desktopFile, strings.NewReader(desktopStr))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
desktopFile.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Make all directories provided if they do not exist
|
|
func makeDirs(dirs ...string) error {
|
|
// For each directory
|
|
for _, dir := range dirs {
|
|
// Create directory and parents if required
|
|
err := os.MkdirAll(dir, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|