137 lines
3.5 KiB
Go
137 lines
3.5 KiB
Go
package macos
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/xml"
|
|
"image"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"aego/app/export/icon"
|
|
"aego/core/errs"
|
|
)
|
|
|
|
type Info struct {
|
|
Name string
|
|
Executable string
|
|
ID string
|
|
Version string
|
|
Build string
|
|
MinOS string
|
|
Category string
|
|
Copyright string
|
|
}
|
|
|
|
var icnsTypes = []struct {
|
|
typ string
|
|
size int
|
|
}{
|
|
{"icp4", 16}, {"icp5", 32}, {"icp6", 64}, {"ic07", 128}, {"ic08", 256}, {"ic09", 512}, {"ic10", 1024},
|
|
{"ic11", 32}, {"ic12", 64}, {"ic13", 256}, {"ic14", 512},
|
|
}
|
|
|
|
func BundleID(module string) string {
|
|
var b strings.Builder
|
|
for _, r := range strings.ToLower(module) {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', r == '-', r == '.':
|
|
b.WriteRune(r)
|
|
default:
|
|
b.WriteByte('.')
|
|
}
|
|
}
|
|
id := strings.Trim(b.String(), ".")
|
|
if id == "" {
|
|
id = "game"
|
|
}
|
|
return "dev.aego." + id
|
|
}
|
|
|
|
func esc(s string) string {
|
|
var b bytes.Buffer
|
|
xml.EscapeText(&b, []byte(s))
|
|
return b.String()
|
|
}
|
|
|
|
func Plist(i Info, iconFile string) []byte {
|
|
if i.Build == "" {
|
|
i.Build = i.Version
|
|
}
|
|
var b strings.Builder
|
|
b.WriteString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
|
|
b.WriteString("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n")
|
|
b.WriteString("<plist version=\"1.0\">\n<dict>\n")
|
|
str := func(k, v string) {
|
|
if v != "" {
|
|
b.WriteString("\t<key>" + k + "</key>\n\t<string>" + esc(v) + "</string>\n")
|
|
}
|
|
}
|
|
str("CFBundleName", i.Name)
|
|
str("CFBundleDisplayName", i.Name)
|
|
str("CFBundleExecutable", i.Executable)
|
|
str("CFBundleIdentifier", i.ID)
|
|
str("CFBundleVersion", i.Build)
|
|
str("CFBundleShortVersionString", i.Version)
|
|
str("CFBundlePackageType", "APPL")
|
|
str("CFBundleInfoDictionaryVersion", "6.0")
|
|
str("CFBundleIconFile", iconFile)
|
|
str("LSMinimumSystemVersion", i.MinOS)
|
|
str("LSApplicationCategoryType", i.Category)
|
|
str("NSHumanReadableCopyright", i.Copyright)
|
|
b.WriteString("\t<key>NSHighResolutionCapable</key>\n\t<true/>\n")
|
|
b.WriteString("</dict>\n</plist>\n")
|
|
return []byte(b.String())
|
|
}
|
|
|
|
func ICNS(src image.Image) ([]byte, error) {
|
|
cache := map[int][]byte{}
|
|
var body bytes.Buffer
|
|
for _, t := range icnsTypes {
|
|
data, ok := cache[t.size]
|
|
if !ok {
|
|
var err error
|
|
if data, err = icon.PNG(icon.Resize(src, t.size, t.size)); err != nil {
|
|
return nil, err
|
|
}
|
|
cache[t.size] = data
|
|
}
|
|
var h [8]byte
|
|
copy(h[:4], t.typ)
|
|
binary.BigEndian.PutUint32(h[4:], uint32(8+len(data)))
|
|
body.Write(h[:])
|
|
body.Write(data)
|
|
}
|
|
out := make([]byte, 8, 8+body.Len())
|
|
copy(out, "icns")
|
|
binary.BigEndian.PutUint32(out[4:], uint32(8+body.Len()))
|
|
return append(out, body.Bytes()...), nil
|
|
}
|
|
|
|
func Fill(app string, i Info, img image.Image) error {
|
|
contents := filepath.Join(app, "Contents")
|
|
res := filepath.Join(contents, "Resources")
|
|
if err := os.MkdirAll(res, 0o755); err != nil {
|
|
return errs.Wrap(err, errs.VFSIO, "cannot create the app bundle")
|
|
}
|
|
iconFile := ""
|
|
if img != nil {
|
|
data, err := ICNS(img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(filepath.Join(res, "icon.icns"), data, 0o644); err != nil {
|
|
return errs.Wrap(err, errs.VFSIO, "cannot write the app icon")
|
|
}
|
|
iconFile = "icon"
|
|
}
|
|
if err := os.WriteFile(filepath.Join(contents, "Info.plist"), Plist(i, iconFile), 0o644); err != nil {
|
|
return errs.Wrap(err, errs.VFSIO, "cannot write Info.plist")
|
|
}
|
|
if err := os.WriteFile(filepath.Join(contents, "PkgInfo"), []byte("APPL????"), 0o644); err != nil {
|
|
return errs.Wrap(err, errs.VFSIO, "cannot write PkgInfo")
|
|
}
|
|
return nil
|
|
}
|