diff --git a/color_windows.go b/color_windows.go index 64b840a..a47c097 100644 --- a/color_windows.go +++ b/color_windows.go @@ -46,15 +46,18 @@ func selectColor(options []Option) (color.Color, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - if opts.title != "" { - hook, err := hookDialogTitle(opts.title) - if hook == 0 { + if opts.ctx != nil || opts.title != "" { + unhook, err := hookDialogTitle(opts.ctx, opts.title) + if err != nil { return nil, err } - defer unhookWindowsHookEx.Call(hook) + defer unhook() } s, _, _ := chooseColor.Call(uintptr(unsafe.Pointer(&args))) + if opts.ctx != nil && opts.ctx.Err() != nil { + return nil, opts.ctx.Err() + } if s == 0 { return nil, commDlgError() } diff --git a/file_windows.go b/file_windows.go index 2455e3e..b1551e9 100644 --- a/file_windows.go +++ b/file_windows.go @@ -46,7 +46,18 @@ func selectFile(options []Option) (string, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() + if opts.ctx != nil { + unhook, err := hookDialog(opts.ctx, nil) + if err != nil { + return "", err + } + defer unhook() + } + s, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args))) + if opts.ctx != nil && opts.ctx.Err() != nil { + return "", opts.ctx.Err() + } if s == 0 { return "", commDlgError() } @@ -82,7 +93,18 @@ func selectFileMutiple(options []Option) ([]string, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() + if opts.ctx != nil { + unhook, err := hookDialog(opts.ctx, nil) + if err != nil { + return nil, err + } + defer unhook() + } + s, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args))) + if opts.ctx != nil && opts.ctx.Err() != nil { + return nil, opts.ctx.Err() + } if s == 0 { return nil, commDlgError() } @@ -149,7 +171,18 @@ func selectFileSave(options []Option) (string, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() + if opts.ctx != nil { + unhook, err := hookDialog(opts.ctx, nil) + if err != nil { + return "", err + } + defer unhook() + } + s, _, _ := getSaveFileName.Call(uintptr(unsafe.Pointer(&args))) + if opts.ctx != nil && opts.ctx.Err() != nil { + return "", opts.ctx.Err() + } if s == 0 { return "", commDlgError() } diff --git a/msg_windows.go b/msg_windows.go index e3144d5..45ccdcf 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -1,6 +1,7 @@ package zenity import ( + "context" "runtime" "syscall" "unsafe" @@ -41,21 +42,24 @@ func message(kind messageKind, text string, options []Option) (bool, error) { } } - if opts.okLabel != "" || opts.cancelLabel != "" || opts.extraButton != "" { + if opts.ctx != nil || opts.okLabel != "" || opts.cancelLabel != "" || opts.extraButton != "" { runtime.LockOSThread() defer runtime.UnlockOSThread() - hook, err := hookMessageLabels(kind, opts) - if hook == 0 { + unhook, err := hookMessageLabels(kind, opts) + if err != nil { return false, err } - defer unhookWindowsHookEx.Call(hook) + defer unhook() } s, _, err := messageBox.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(opts.title))), flags) + if opts.ctx != nil && opts.ctx.Err() != nil { + return false, opts.ctx.Err() + } if s == 0 { return false, err } @@ -68,46 +72,35 @@ func message(kind messageKind, text string, options []Option) (bool, error) { return false, nil } -func hookMessageLabels(kind messageKind, opts options) (hook uintptr, err error) { - tid, _, _ := getCurrentThreadId.Call() - hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET - syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { - if lparam.Message == 0x0110 { // WM_INITDIALOG - name := [7]uint16{} - getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) - if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box - enumChildWindows.Call(lparam.Wnd, - syscall.NewCallback(func(wnd, lparam uintptr) uintptr { - name := [7]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 1, 6: // IDOK, IDYES - text = opts.okLabel - case 2: // IDCANCEL - if kind == questionKind { - text = opts.cancelLabel - } else if opts.extraButton != "" { - text = opts.extraButton - } else { - text = opts.okLabel - } - case 7: // IDNO - text = opts.extraButton - } - if text != "" { - ptr := syscall.StringToUTF16Ptr(text) - setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr))) - } - } - return 1 - }), 0) +func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFunc, err error) { + return hookDialog(opts.ctx, func(wnd uintptr) { + enumChildWindows.Call(wnd, + syscall.NewCallback(func(wnd, lparam uintptr) uintptr { + 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 1, 6: // IDOK, IDYES + text = opts.okLabel + case 2: // IDCANCEL + if kind == questionKind { + text = opts.cancelLabel + } else if opts.extraButton != "" { + text = opts.extraButton + } else { + text = opts.okLabel + } + case 7: // IDNO + text = opts.extraButton + } + if text != "" { + ptr := syscall.StringToUTF16Ptr(text) + setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr))) + } } - } - next, _, _ := callNextHookEx.Call(hook, uintptr(code), wparam, uintptr(unsafe.Pointer(lparam))) - return next - }), 0, tid) - return + return 1 + }), 0) + }) } diff --git a/util_windows.go b/util_windows.go index 2e2873a..46d6113 100644 --- a/util_windows.go +++ b/util_windows.go @@ -1,7 +1,9 @@ package zenity import ( + "context" "fmt" + "sync" "syscall" "unsafe" ) @@ -49,22 +51,73 @@ type _CWPRETSTRUCT struct { Wnd uintptr } -func hookDialogTitle(title string) (hook uintptr, err error) { +func hookDialog(ctx context.Context, initDialog func(wnd uintptr)) (unhook context.CancelFunc, err error) { + if ctx != nil && ctx.Err() != nil { + return nil, ctx.Err() + } + + var mtx sync.Mutex + var hook, wnd uintptr tid, _, _ := getCurrentThreadId.Call() hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { if lparam.Message == 0x0110 { // WM_INITDIALOG - name := [7]uint16{} + 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 - ptr := syscall.StringToUTF16Ptr(title) - setWindowText.Call(lparam.Wnd, uintptr(unsafe.Pointer(ptr))) + var close bool + + mtx.Lock() + if ctx != nil && ctx.Err() != nil { + close = true + } else { + wnd = lparam.Wnd + } + mtx.Unlock() + + if close { + sendMessage.Call(lparam.Wnd, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0) + } else if initDialog != nil { + initDialog(lparam.Wnd) + } } } next, _, _ := callNextHookEx.Call(hook, uintptr(code), wparam, uintptr(unsafe.Pointer(lparam))) return next }), 0, tid) - return + + if hook == 0 { + return nil, err + } + if ctx == nil { + return func() { unhookWindowsHookEx.Call(hook) }, nil + } + + wait := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + mtx.Lock() + w := wnd + mtx.Unlock() + + if w != 0 { + sendMessage.Call(w, 0x0112 /* WM_SYSCOMMAND */, 0xf060 /* SC_CLOSE */, 0) + } + case <-wait: + } + }() + return func() { + unhookWindowsHookEx.Call(hook) + close(wait) + }, nil +} + +func hookDialogTitle(ctx context.Context, title string) (unhook context.CancelFunc, err error) { + return hookDialog(ctx, func(wnd uintptr) { + ptr := syscall.StringToUTF16Ptr(title) + setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr))) + }) } type _COMObject struct{} diff --git a/zenity.go b/zenity.go index 4f04e0d..764bb5b 100644 --- a/zenity.go +++ b/zenity.go @@ -73,7 +73,7 @@ func Title(title string) Option { // DialogIcon is the enumeration for dialog icons. type DialogIcon int -// Icons for +// The stock dialog icons. const ( ErrorIcon DialogIcon = iota + 1 WarningIcon @@ -87,6 +87,8 @@ func Icon(icon DialogIcon) Option { } // Context returns an Option to set a Context that can dismiss the dialog. +// +// Dialogs dismissed by the Context return Context.Err. func Context(ctx context.Context) Option { return funcOption(func(o *options) { o.ctx = ctx }) }