Icon from EXE (windows), see #35.

This commit is contained in:
Nuno Cruces 2022-07-26 16:56:43 +01:00
parent 566fbb4e60
commit fc4cc53c87
10 changed files with 54 additions and 23 deletions

View file

@ -41,7 +41,7 @@ func (dlg *calendarDialog) setup(text string, opts options) (time.Time, error) {
defer setup(owner)()
dlg.font = getFont()
defer dlg.font.delete()
icon := getIcon(opts.windowIcon)
icon, _ := getIcon(opts.windowIcon)
defer icon.delete()
if opts.ctx != nil && opts.ctx.Err() != nil {

View file

@ -40,7 +40,7 @@ func (dlg *entryDialog) setup(text string, opts options) (string, error) {
defer setup(owner)()
dlg.font = getFont()
defer dlg.font.delete()
icon := getIcon(opts.windowIcon)
icon, _ := getIcon(opts.windowIcon)
defer icon.delete()
if opts.ctx != nil && opts.ctx.Err() != nil {

View file

@ -284,6 +284,7 @@ func (u *IShellItemArray) GetItemAt(index uint32) (item *IShellItem, err error)
return
}
//sys ExtractAssociatedIcon(instance Handle, path *uint16, icon *uint16) (ret Handle, err error) = shell32.ExtractAssociatedIconW
//sys SHBrowseForFolder(bi *BROWSEINFO) (ret *IDLIST) = shell32.SHBrowseForFolder
//sys SHCreateItemFromParsingName(path *uint16, bc *IBindCtx, iid uintptr, item **IShellItem) (res error) = shell32.SHCreateItemFromParsingName
//sys ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ok bool) = shell32.Shell_NotifyIconW

View file

@ -65,6 +65,7 @@ var (
procGlobalFree = modkernel32.NewProc("GlobalFree")
procReleaseActCtx = modkernel32.NewProc("ReleaseActCtx")
procCoCreateInstance = modole32.NewProc("CoCreateInstance")
procExtractAssociatedIconW = modshell32.NewProc("ExtractAssociatedIconW")
procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolder")
procSHCreateItemFromParsingName = modshell32.NewProc("SHCreateItemFromParsingName")
procSHGetPathFromIDListEx = modshell32.NewProc("SHGetPathFromIDListEx")
@ -235,6 +236,15 @@ func CoCreateInstance(clsid uintptr, unkOuter *IUnknown, clsContext int32, iid u
return
}
func ExtractAssociatedIcon(instance Handle, path *uint16, icon *uint16) (ret Handle, err error) {
r0, _, e1 := syscall.Syscall(procExtractAssociatedIconW.Addr(), 3, uintptr(instance), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(icon)))
ret = Handle(r0)
if ret == 0 {
err = errnoErr(e1)
}
return
}
func SHBrowseForFolder(bi *BROWSEINFO) (ret *IDLIST) {
r0, _, _ := syscall.Syscall(procSHBrowseForFolder.Addr(), 1, uintptr(unsafe.Pointer(bi)), 0, 0)
ret = (*IDLIST)(unsafe.Pointer(r0))

View file

@ -57,7 +57,7 @@ func (dlg *listDialog) setup(text string, opts options) ([]string, error) {
defer setup(owner)()
dlg.font = getFont()
defer dlg.font.delete()
icon := getIcon(opts.windowIcon)
icon, _ := getIcon(opts.windowIcon)
defer icon.delete()
if opts.ctx != nil && opts.ctx.Err() != nil {

View file

@ -104,11 +104,15 @@ func hookMessageDialog(opts options) (context.CancelFunc, error) {
}
}
}
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, init)
if err != nil || opts.icon == nil {
return unhook, err
icon, err := getIcon(opts.icon)
if err != nil {
return nil, err
}
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, init)
if err != nil {
icon.delete()
return nil, err
}
icon = getIcon(opts.icon)
return func() {
icon.delete()
unhook()

View file

@ -38,7 +38,7 @@ func notify(text string, opts options) error {
case ErrorIcon:
args.InfoFlags |= win.NIIF_ERROR
default:
icon := getIcon(opts.icon)
icon, _ := getIcon(opts.icon)
if icon.handle != 0 {
defer icon.delete()
args.Icon = icon.handle

View file

@ -120,7 +120,7 @@ func (dlg *progressDialog) setup(opts options) error {
defer setup(owner)()
dlg.font = getFont()
defer dlg.font.delete()
icon := getIcon(opts.windowIcon)
icon, _ := getIcon(opts.windowIcon)
defer icon.delete()
if opts.ctx != nil && opts.ctx.Err() != nil {

View file

@ -50,7 +50,7 @@ func (dlg *passwordDialog) setup(opts options) (string, string, error) {
defer setup(owner)()
dlg.font = getFont()
defer dlg.font.delete()
icon := getIcon(opts.windowIcon)
icon, _ := getIcon(opts.windowIcon)
defer icon.delete()
if opts.ctx != nil && opts.ctx.Err() != nil {

View file

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
@ -12,6 +13,7 @@ import (
"unsafe"
"github.com/ncruces/zenity/internal/win"
"golang.org/x/sys/windows"
)
const (
@ -106,12 +108,13 @@ func newDialogHook(ctx context.Context, icon any, title *string, init func(wnd w
if hk == 0 {
return nil, err
}
ico, _ := getIcon(icon)
hook := dialogHook{
ctx: ctx,
tid: tid,
hook: hk,
icon: getIcon(icon),
icon: ico,
title: title,
init: init,
}
@ -253,8 +256,7 @@ type icon struct {
destroy bool
}
func getIcon(i any) icon {
var res icon
func getIcon(i any) (icon icon, err error) {
var resource uintptr
switch i {
case ErrorIcon:
@ -267,38 +269,52 @@ func getIcon(i any) icon {
resource = win.IDI_INFORMATION
}
if resource != 0 {
res.handle, _ = win.LoadIcon(0, resource)
return res
icon.handle, err = win.LoadIcon(0, resource)
return icon, err
}
path, ok := i.(string)
if !ok {
return res
return icon, nil
}
data, err := os.ReadFile(path)
if err != nil {
return res
return icon, err
}
switch {
case bytes.HasPrefix(data, []byte("\x00\x00\x01\x00")):
res.handle, _ = win.LoadImage(0,
icon.handle, err = win.LoadImage(0,
strptr(path),
win.IMAGE_ICON, 0, 0,
win.LR_LOADFROMFILE|win.LR_DEFAULTSIZE)
res.destroy = true
icon.destroy = true
case bytes.HasPrefix(data, []byte("\x89PNG\r\n\x1a\n")):
res.handle, _ = win.CreateIconFromResourceEx(
icon.handle, err = win.CreateIconFromResourceEx(
data, true, 0x00030000, 0, 0,
win.LR_DEFAULTSIZE)
res.destroy = true
icon.destroy = true
case bytes.HasPrefix(data, []byte("MZ")):
var instance windows.Handle
instance, err = win.GetModuleHandle(nil)
if err != nil {
break
}
path, err = filepath.Abs(path)
if err != nil {
break
}
var i uint16
icon.handle, err = win.ExtractAssociatedIcon(
instance, strptr(path), &i)
icon.destroy = true
}
return res
return icon, err
}
func (i *icon) delete() {
if i.destroy && i.handle != 0 {
if i.handle != 0 && i.destroy {
win.DestroyIcon(i.handle)
i.handle = 0
}