diff --git a/cmd/zenity/main.go b/cmd/zenity/main.go index 099c990..d310331 100644 --- a/cmd/zenity/main.go +++ b/cmd/zenity/main.go @@ -395,6 +395,8 @@ func loadFlags() []zenity.Option { var ico zenity.DialogIcon switch icon { + case unspecified: + ico = 0 case "error", "dialog-error": ico = zenity.ErrorIcon case "info", "dialog-information": @@ -405,7 +407,7 @@ func loadFlags() []zenity.Option { ico = zenity.WarningIcon case "dialog-password": ico = zenity.PasswordIcon - case unspecified: + case "": ico = zenity.NoIcon default: opts = append(opts, zenity.CustomIcon(icon)) diff --git a/msg_windows.go b/msg_windows.go index e2733fc..d3740c7 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -1,14 +1,19 @@ package zenity import ( + "bytes" "context" + "os" "syscall" "unsafe" ) var ( - messageBox = user32.NewProc("MessageBoxW") - getDlgCtrlID = user32.NewProc("GetDlgCtrlID") + messageBox = user32.NewProc("MessageBoxW") + 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 { @@ -55,8 +60,8 @@ func message(kind messageKind, text string, opts options) error { defer setup()() - if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil { - unhook, err := hookMessageLabels(kind, opts) + if opts.ctx != nil || opts.okLabel != nil || opts.cancelLabel != nil || opts.extraButton != nil || opts.customIcon != "" { + unhook, err := hookMessageDialog(kind, opts) if err != nil { 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) { enumChildWindows.Call(wnd, - syscall.NewCallback(hookMessageLabelsCallback), + syscall.NewCallback(hookMessageDialogCallback), uintptr(unsafe.Pointer(&opts))) }) } -func hookMessageLabelsCallback(wnd uintptr, lparam *options) uintptr { - var name [8]uint16 - getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) - if syscall.UTF16ToString(name[:]) == "Button" { - ctl, _, _ := getDlgCtrlID.Call(wnd) - var text *string - switch ctl { - case _IDOK, _IDYES: - text = lparam.okLabel - case _IDCANCEL: - text = lparam.cancelLabel - case _IDNO: - text = lparam.extraButton +func hookMessageDialogCallback(wnd uintptr, lparam *options) uintptr { + ctl, _, _ := getDlgCtrlID.Call(wnd) + + var text *string + switch ctl { + case _IDOK, _IDYES: + text = lparam.okLabel + case _IDCANCEL: + text = lparam.cancelLabel + 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 diff --git a/util_windows.go b/util_windows.go index 2a926b3..6752f94 100644 --- a/util_windows.go +++ b/util_windows.go @@ -52,7 +52,6 @@ var ( isDialogMessage = user32.NewProc("IsDialogMessageW") dispatchMessage = user32.NewProc("DispatchMessageW") translateMessage = user32.NewProc("TranslateMessage") - getClassName = user32.NewProc("GetClassNameW") unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx") setWindowsHookEx = user32.NewProc("SetWindowsHookExW") callNextHookEx = user32.NewProc("CallNextHookEx") @@ -174,7 +173,7 @@ type dialogHook struct { func newDialogHook(ctx context.Context, initDialog func(wnd uintptr)) (*dialogHook, error) { tid, _, _ := getCurrentThreadId.Call() - hk, _, err := setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET + hk, _, err := setWindowsHookEx.Call(5, // WH_CBT syscall.NewCallback(dialogHookProc), 0, tid) if hk == 0 { return nil, err @@ -195,23 +194,19 @@ func newDialogHook(ctx context.Context, initDialog func(wnd uintptr)) (*dialogHo return &hook, nil } -func dialogHookProc(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { - if lparam.Message == 0x0110 { // WM_INITDIALOG - var name [8]uint16 - getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) - if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box - tid, _, _ := getCurrentThreadId.Call() - hook := (*dialogHook)(loadBackRef(tid)) - atomic.StoreUintptr(&hook.wnd, lparam.Wnd) - if hook.ctx != nil && hook.ctx.Err() != nil { - sendMessage.Call(lparam.Wnd, _WM_SYSCOMMAND, _SC_CLOSE, 0) - } else if hook.init != nil { - hook.init(lparam.Wnd) - } +func dialogHookProc(code int32, wparam, lparam uintptr) uintptr { + if code == 5 { // HCBT_ACTIVATE + tid, _, _ := getCurrentThreadId.Call() + hook := (*dialogHook)(loadBackRef(tid)) + atomic.StoreUintptr(&hook.wnd, wparam) + if hook.ctx != nil && hook.ctx.Err() != nil { + sendMessage.Call(wparam, _WM_SYSCOMMAND, _SC_CLOSE, 0) + } else if hook.init != nil { + hook.init(wparam) } } next, _, _ := callNextHookEx.Call( - 0, uintptr(code), wparam, uintptr(unsafe.Pointer(lparam))) + 0, uintptr(code), wparam, lparam) return next } @@ -422,15 +417,6 @@ type _INITCOMMONCONTROLSEX struct { 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 type _LOGFONT struct { Height int32