Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/fyne/internal/commands/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "fyne.io/tools/cmd/fyne/internal/metadata"

type appData struct {
icon, Name string
darkIcon string
AppID, AppVersion string
AppBuild int
ResGoString string
Expand Down Expand Up @@ -33,6 +34,9 @@ func (a *appData) mergeMetadata(data *metadata.FyneApp) {
if a.icon == "" {
a.icon = data.Details.Icon
}
if a.darkIcon == "" {
a.darkIcon = data.Details.DarkIcon
}
if a.Name == "" {
a.Name = data.Details.Name
}
Expand Down
7 changes: 7 additions & 0 deletions cmd/fyne/internal/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ var stringFlags = map[string]func(*string) cli.Flag{
Destination: dst,
}
},
"dark-icon": func(dst *string) cli.Flag {
return &cli.StringFlag{
Name: "dark-icon",
Usage: "set name of the application icon file in dark mode (where supported)",
Destination: dst,
}
},
"developer": func(dst *string) cli.Flag {
return &cli.StringFlag{
Name: "developer",
Expand Down
79 changes: 68 additions & 11 deletions cmd/fyne/internal/commands/package-darwin.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package commands

import (
"bytes"
"encoding/binary"
"fmt"
"image"
"image/color"
"io"
"os"
"path/filepath"
"strings"
Expand All @@ -24,6 +27,12 @@ type darwinData struct {
Languages []string
}

const (
icnsHeaderSize = 8
icnsMagicSize = 4
icnsOSTypeDark = "\xFD\xD9\x2F\xA8"
)

func darwinLangs(langs []string) []string {
r := make([]string, len(langs))
for n, lang := range langs {
Expand Down Expand Up @@ -65,14 +74,29 @@ func (p *Packager) packageDarwin() (err error) {
resDir := util.EnsureSubDir(contentsDir, "Resources")
icnsPath := filepath.Join(resDir, "icon.icns")

img, err := os.Open(p.icon)
srcImg, err := loadIcon(p.icon)
if err != nil {
return fmt.Errorf("failed to open source image \"%s\": %w", p.icon, err)
return err
}
defer img.Close()
srcImg, _, err := image.Decode(img)
if err != nil {
return fmt.Errorf("failed to decode source image: %w", err)
if !p.rawIcon {
srcImg = processMacOSIcon(srcImg)
}
buf := &bytes.Buffer{}
if err := icns.Encode(buf, srcImg); err != nil {
return fmt.Errorf("failed to encode icns: %w", err)
}

if p.darkIcon != "" {
darkSrcImg, err := loadIcon(p.darkIcon)
if err != nil {
return err
}
if !p.rawIcon {
darkSrcImg = processMacOSIcon(darkSrcImg)
}
if err := injectDarkIcon(darkSrcImg, buf); err != nil {
return err
}
}
dest, err := os.Create(icnsPath)
if err != nil {
Expand All @@ -83,16 +107,28 @@ func (p *Packager) packageDarwin() (err error) {
err = r
}
}()
if !p.rawIcon {
srcImg = processMacOSIcon(srcImg)
}
if err := icns.Encode(dest, srcImg); err != nil {
return fmt.Errorf("failed to encode icns: %w", err)
if _, err := io.Copy(dest, buf); err != nil {
return fmt.Errorf("failed to write destination file: %w", err)
}

return nil
}

func loadIcon(icon string) (image.Image, error) {
f, err := os.Open(icon)
if err != nil {
return nil, fmt.Errorf("failed to open source image %q: %w", icon, err)
}
defer f.Close()

img, _, err := image.Decode(f)
if err != nil {
return nil, fmt.Errorf("failed to decode source image: %w", err)
}

return img, nil
}

func processMacOSIcon(in image.Image) image.Image {
size := 1024
border := 100
Expand All @@ -113,3 +149,24 @@ func processMacOSIcon(in image.Image) image.Image {

return dc.Image()
}

func injectDarkIcon(darkImg image.Image, buf *bytes.Buffer) error {
buf2 := &bytes.Buffer{}
if err := icns.Encode(buf2, darkImg); err != nil {
return fmt.Errorf("failed to encode icns: %w", err)
}

if _, err := buf.WriteString(icnsOSTypeDark); err != nil {
return fmt.Errorf("failed to append dark icon header: %w", err)
}
if err := binary.Write(buf, binary.BigEndian, uint32(buf2.Len()-icnsHeaderSize)); err != nil {
return fmt.Errorf("failed to append dark icon length: %w", err)
}
if _, err := buf.Write(buf2.Bytes()[icnsHeaderSize:]); err != nil {
return fmt.Errorf("failed to append dark icon data: %w", err)
}

binary.BigEndian.PutUint32(buf.Bytes()[icnsMagicSize:], uint32(buf.Len()))

return nil
}
1 change: 1 addition & 0 deletions cmd/fyne/internal/commands/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func Package() *cli.Command {
intFlags["app-build"](&p.AppBuild),
stringFlags["src"](&p.srcDir),
stringFlags["icon"](&p.icon),
stringFlags["dark-icon"](&p.darkIcon),
boolFlags["use-raw-icon"](&p.rawIcon),
stringFlags["app-id"](&p.AppID),
stringFlags["certificate"](&p.certificate),
Expand Down
1 change: 1 addition & 0 deletions cmd/fyne/internal/metadata/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type FyneApp struct {
// AppDetails describes the build information, this group may be OS or arch specific
type AppDetails struct {
Icon string `toml:",omitempty"`
DarkIcon string `toml:",omitempty"`
Name, ID string `toml:",omitempty"`
Version string `toml:",omitempty"`
Build int `toml:",omitempty"`
Expand Down
Loading