Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for getting binary icon and name from exe on Windows
- Loading branch information
Showing
10 changed files
with
393 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package icons | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"image" | ||
_ "image/png" // Register png support for image package | ||
|
||
"github.com/fogleman/gg" | ||
_ "github.com/mat/besticon/ico" // Register ico support for image package | ||
) | ||
|
||
// ConvertICOtoPNG converts a an .ico to a .png image. | ||
func ConvertICOtoPNG(ico []byte) (png []byte, err error) { | ||
// Decode the ICO. | ||
icon, _, err := image.Decode(bytes.NewReader(ico)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode ICO: %w", err) | ||
} | ||
|
||
// Convert to raw image. | ||
img := gg.NewContextForImage(icon) | ||
|
||
// Convert to PNG. | ||
imgBuf := &bytes.Buffer{} | ||
err = img.EncodePNG(imgBuf) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to encode PNG: %w", err) | ||
} | ||
|
||
return imgBuf.Bytes(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
//go:build !linux | ||
//go:build !linux && !windows | ||
|
||
package icons | ||
|
||
import "context" | ||
|
||
// FindIcon returns nil, nil for unsupported platforms. | ||
func FindIcon(ctx context.Context, binName string, homeDir string) (*Icon, error) { | ||
return nil, nil | ||
// GetIconAndName returns zero values for unsupported platforms. | ||
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) { | ||
return nil, "", nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package icons | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/tc-hib/winres" | ||
"github.com/tc-hib/winres/version" | ||
) | ||
|
||
// GetIconAndName returns an icon and name of the given binary path. | ||
// Providing the home directory of the user running the process of that binary can improve results. | ||
// Even if an error is returned, the other return values are valid, if set. | ||
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) { | ||
// Get name and png from exe. | ||
png, name, err := getIconAndNamefromRSS(ctx, binPath) | ||
|
||
// Fall back to name generation if name is not set. | ||
if name == "" { | ||
name = GenerateBinaryNameFromPath(binPath) | ||
} | ||
|
||
// Handle previous error. | ||
if err != nil { | ||
return nil, name, err | ||
} | ||
|
||
// Update profile icon and return icon object. | ||
filename, err := UpdateProfileIcon(png, "png") | ||
if err != nil { | ||
return nil, name, fmt.Errorf("failed to store icon: %w", err) | ||
} | ||
|
||
return &Icon{ | ||
Type: IconTypeAPI, | ||
Value: filename, | ||
}, name, nil | ||
} | ||
|
||
func getIconAndNamefromRSS(ctx context.Context, binPath string) (png []byte, name string, err error) { | ||
// Open .exe file. | ||
exeFile, err := os.Open(binPath) | ||
if err != nil { | ||
if errors.Is(err, os.ErrNotExist) { | ||
return nil, "", nil | ||
} | ||
return nil, "", fmt.Errorf("failed to open exe %s to get icon: %w", binPath, err) | ||
} | ||
defer exeFile.Close() //nolint:errcheck | ||
|
||
// Load .exe resources. | ||
rss, err := winres.LoadFromEXE(exeFile) | ||
if err != nil { | ||
return nil, "", fmt.Errorf("failed to get rss: %w", err) | ||
} | ||
|
||
// DEBUG: Print all available resources: | ||
// rss.Walk(func(typeID, resID winres.Identifier, langID uint16, data []byte) bool { | ||
// fmt.Printf("typeID=%d resID=%d langID=%d\n", typeID, resID, langID) | ||
// return true | ||
// }) | ||
|
||
// Get first icon. | ||
var ( | ||
icon *winres.Icon | ||
iconErr error | ||
) | ||
rss.WalkType(winres.RT_GROUP_ICON, func(resID winres.Identifier, langID uint16, _ []byte) bool { | ||
icon, iconErr = rss.GetIconTranslation(resID, langID) | ||
return iconErr != nil | ||
}) | ||
if iconErr != nil { | ||
return nil, "", fmt.Errorf("failed to get icon: %w", err) | ||
} | ||
// Convert icon. | ||
icoBuf := &bytes.Buffer{} | ||
err = icon.SaveICO(icoBuf) | ||
if err != nil { | ||
return nil, "", fmt.Errorf("failed to save ico: %w", err) | ||
} | ||
png, err = ConvertICOtoPNG(icoBuf.Bytes()) | ||
if err != nil { | ||
return nil, "", fmt.Errorf("failed to convert ico to png: %w", err) | ||
} | ||
|
||
// Get name from version record. | ||
var ( | ||
versionInfo *version.Info | ||
versionInfoErr error | ||
) | ||
rss.WalkType(winres.RT_VERSION, func(resID winres.Identifier, langID uint16, data []byte) bool { | ||
versionInfo, versionInfoErr = version.FromBytes(data) | ||
switch { | ||
case versionInfoErr != nil: | ||
return true | ||
case versionInfo == nil: | ||
return true | ||
} | ||
|
||
// Get metadata table and main language. | ||
table := versionInfo.Table().GetMainTranslation() | ||
if table == nil { | ||
return true | ||
} | ||
|
||
name = table[version.ProductName] | ||
return name == "" | ||
}) | ||
name = cleanFileDescription(name) | ||
|
||
return png, name, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package icons | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestFindIcon(t *testing.T) { | ||
if testing.Short() { | ||
t.Skip("test meant for compiling and running on desktop") | ||
} | ||
t.Parallel() | ||
|
||
binName := os.Args[len(os.Args)-1] | ||
t.Logf("getting name and icon for %s", binName) | ||
png, name, err := getIconAndNamefromRSS(context.Background(), binName) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
t.Logf("name: %s", name) | ||
err = os.WriteFile("icon.png", png, 0o0600) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
Oops, something went wrong.