From fc4cc53c87aabc932d03693bf5c0fa482df22dd0 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Tue, 26 Jul 2022 16:56:43 +0100 Subject: [PATCH] Icon from EXE (windows), see #35. --- date_windows.go | 2 +- entry_windows.go | 2 +- internal/win/shell32.go | 1 + internal/win/zsyscall_windows.go | 10 ++++++++ list_windows.go | 2 +- msg_windows.go | 12 ++++++--- notify_windows.go | 2 +- progress_windows.go | 2 +- pwd_windows.go | 2 +- util_windows.go | 42 ++++++++++++++++++++++---------- 10 files changed, 54 insertions(+), 23 deletions(-) diff --git a/date_windows.go b/date_windows.go index 188347d..b59ea5c 100644 --- a/date_windows.go +++ b/date_windows.go @@ -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 { diff --git a/entry_windows.go b/entry_windows.go index 0bc30ea..2da2931 100644 --- a/entry_windows.go +++ b/entry_windows.go @@ -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 { diff --git a/internal/win/shell32.go b/internal/win/shell32.go index 9f0e79e..be88997 100644 --- a/internal/win/shell32.go +++ b/internal/win/shell32.go @@ -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 diff --git a/internal/win/zsyscall_windows.go b/internal/win/zsyscall_windows.go index 5e84641..34884f6 100644 --- a/internal/win/zsyscall_windows.go +++ b/internal/win/zsyscall_windows.go @@ -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)) diff --git a/list_windows.go b/list_windows.go index a9245b9..0023cab 100644 --- a/list_windows.go +++ b/list_windows.go @@ -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 { diff --git a/msg_windows.go b/msg_windows.go index 3ec6733..1f292ea 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -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() diff --git a/notify_windows.go b/notify_windows.go index a8c0aa2..2dde8e1 100644 --- a/notify_windows.go +++ b/notify_windows.go @@ -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 diff --git a/progress_windows.go b/progress_windows.go index 4485e26..7b80305 100644 --- a/progress_windows.go +++ b/progress_windows.go @@ -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 { diff --git a/pwd_windows.go b/pwd_windows.go index 9e915a0..0d46c12 100644 --- a/pwd_windows.go +++ b/pwd_windows.go @@ -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 { diff --git a/util_windows.go b/util_windows.go index b9f2235..6c37dd5 100644 --- a/util_windows.go +++ b/util_windows.go @@ -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 }