Custom icons (windows).

This commit is contained in:
Nuno Cruces 2022-05-09 15:49:32 +01:00
parent 9539a7da29
commit 53d7c0954c
3 changed files with 60 additions and 47 deletions

View file

@ -395,6 +395,8 @@ func loadFlags() []zenity.Option {
var ico zenity.DialogIcon var ico zenity.DialogIcon
switch icon { switch icon {
case unspecified:
ico = 0
case "error", "dialog-error": case "error", "dialog-error":
ico = zenity.ErrorIcon ico = zenity.ErrorIcon
case "info", "dialog-information": case "info", "dialog-information":
@ -405,7 +407,7 @@ func loadFlags() []zenity.Option {
ico = zenity.WarningIcon ico = zenity.WarningIcon
case "dialog-password": case "dialog-password":
ico = zenity.PasswordIcon ico = zenity.PasswordIcon
case unspecified: case "":
ico = zenity.NoIcon ico = zenity.NoIcon
default: default:
opts = append(opts, zenity.CustomIcon(icon)) opts = append(opts, zenity.CustomIcon(icon))

View file

@ -1,14 +1,19 @@
package zenity package zenity
import ( import (
"bytes"
"context" "context"
"os"
"syscall" "syscall"
"unsafe" "unsafe"
) )
var ( var (
messageBox = user32.NewProc("MessageBoxW") messageBox = user32.NewProc("MessageBoxW")
getDlgCtrlID = user32.NewProc("GetDlgCtrlID") getDlgCtrlID = user32.NewProc("GetDlgCtrlID")
loadImage = user32.NewProc("LoadImageW")
destroyIcon = user32.NewProc("DestroyIcon")
createIconFromResource = user32.NewProc("CreateIconFromResource")
) )
func message(kind messageKind, text string, opts options) error { func message(kind messageKind, text string, opts options) error {
@ -55,8 +60,8 @@ func message(kind messageKind, text string, opts options) error {
defer setup()() defer setup()()
if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil { if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil || opts.customIcon != "" {
unhook, err := hookMessageLabels(kind, opts) unhook, err := hookMessageDialog(kind, opts)
if err != nil { if err != nil {
return err return err
} }
@ -85,30 +90,50 @@ func message(kind messageKind, text string, opts options) error {
} }
} }
func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFunc, err error) { func hookMessageDialog(kind messageKind, opts options) (unhook context.CancelFunc, err error) {
return hookDialog(opts.ctx, func(wnd uintptr) { return hookDialog(opts.ctx, func(wnd uintptr) {
enumChildWindows.Call(wnd, enumChildWindows.Call(wnd,
syscall.NewCallback(hookMessageLabelsCallback), syscall.NewCallback(hookMessageDialogCallback),
uintptr(unsafe.Pointer(&opts))) uintptr(unsafe.Pointer(&opts)))
}) })
} }
func hookMessageLabelsCallback(wnd uintptr, lparam *options) uintptr { func hookMessageDialogCallback(wnd uintptr, lparam *options) uintptr {
var name [8]uint16 ctl, _, _ := getDlgCtrlID.Call(wnd)
getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
if syscall.UTF16ToString(name[:]) == "Button" { var text *string
ctl, _, _ := getDlgCtrlID.Call(wnd) switch ctl {
var text *string case _IDOK, _IDYES:
switch ctl { text = lparam.okLabel
case _IDOK, _IDYES: case _IDCANCEL:
text = lparam.okLabel text = lparam.cancelLabel
case _IDCANCEL: case _IDNO:
text = lparam.cancelLabel text = lparam.extraButton
case _IDNO: }
text = lparam.extraButton if text != nil {
setWindowText.Call(wnd, strptr(*text))
}
if ctl == 20 /*IDC_STATIC_OK*/ && lparam.customIcon != "" {
var icon uintptr
data, _ := os.ReadFile(lparam.customIcon)
switch {
case bytes.HasPrefix(data, []byte("\x00\x00\x01\x00")):
icon, _, _ = loadImage.Call(0,
strptr(lparam.customIcon),
1, /*IMAGE_ICON*/
0, 0,
0x00008050 /*LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_SHARED*/)
case bytes.HasPrefix(data, []byte("\x89PNG\r\n\x1a\n")):
icon, _, _ = createIconFromResource.Call(
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
1, 0x00030000)
defer destroyIcon.Call(icon)
} }
if text != nil {
setWindowText.Call(wnd, strptr(*text)) if icon != 0 {
sendMessage.Call(wnd, 0x0170 /*STM_SETICON*/, icon, 0)
} }
} }
return 1 return 1

View file

@ -52,7 +52,6 @@ var (
isDialogMessage = user32.NewProc("IsDialogMessageW") isDialogMessage = user32.NewProc("IsDialogMessageW")
dispatchMessage = user32.NewProc("DispatchMessageW") dispatchMessage = user32.NewProc("DispatchMessageW")
translateMessage = user32.NewProc("TranslateMessage") translateMessage = user32.NewProc("TranslateMessage")
getClassName = user32.NewProc("GetClassNameW")
unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx") unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
setWindowsHookEx = user32.NewProc("SetWindowsHookExW") setWindowsHookEx = user32.NewProc("SetWindowsHookExW")
callNextHookEx = user32.NewProc("CallNextHookEx") callNextHookEx = user32.NewProc("CallNextHookEx")
@ -174,7 +173,7 @@ type dialogHook struct {
func newDialogHook(ctx context.Context, initDialog func(wnd uintptr)) (*dialogHook, error) { func newDialogHook(ctx context.Context, initDialog func(wnd uintptr)) (*dialogHook, error) {
tid, _, _ := getCurrentThreadId.Call() tid, _, _ := getCurrentThreadId.Call()
hk, _, err := setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET hk, _, err := setWindowsHookEx.Call(5, // WH_CBT
syscall.NewCallback(dialogHookProc), 0, tid) syscall.NewCallback(dialogHookProc), 0, tid)
if hk == 0 { if hk == 0 {
return nil, err return nil, err
@ -195,23 +194,19 @@ func newDialogHook(ctx context.Context, initDialog func(wnd uintptr)) (*dialogHo
return &hook, nil return &hook, nil
} }
func dialogHookProc(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { func dialogHookProc(code int32, wparam, lparam uintptr) uintptr {
if lparam.Message == 0x0110 { // WM_INITDIALOG if code == 5 { // HCBT_ACTIVATE
var name [8]uint16 tid, _, _ := getCurrentThreadId.Call()
getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) hook := (*dialogHook)(loadBackRef(tid))
if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box atomic.StoreUintptr(&hook.wnd, wparam)
tid, _, _ := getCurrentThreadId.Call() if hook.ctx != nil && hook.ctx.Err() != nil {
hook := (*dialogHook)(loadBackRef(tid)) sendMessage.Call(wparam, _WM_SYSCOMMAND, _SC_CLOSE, 0)
atomic.StoreUintptr(&hook.wnd, lparam.Wnd) } else if hook.init != nil {
if hook.ctx != nil && hook.ctx.Err() != nil { hook.init(wparam)
sendMessage.Call(lparam.Wnd, _WM_SYSCOMMAND, _SC_CLOSE, 0)
} else if hook.init != nil {
hook.init(lparam.Wnd)
}
} }
} }
next, _, _ := callNextHookEx.Call( next, _, _ := callNextHookEx.Call(
0, uintptr(code), wparam, uintptr(unsafe.Pointer(lparam))) 0, uintptr(code), wparam, lparam)
return next return next
} }
@ -422,15 +417,6 @@ type _INITCOMMONCONTROLSEX struct {
ICC uint32 ICC uint32
} }
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cwpretstruct
type _CWPRETSTRUCT struct {
Result uintptr
LParam uintptr
WParam uintptr
Message uint32
Wnd uintptr
}
// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw
type _LOGFONT struct { type _LOGFONT struct {
Height int32 Height int32