Timeouts, cancellation (windows).

This commit is contained in:
Nuno Cruces 2020-01-30 14:14:42 +00:00 committed by Nuno Cruces
parent 71d3120393
commit 975c82db2a
5 changed files with 139 additions and 55 deletions

View file

@ -46,15 +46,18 @@ func selectColor(options []Option) (color.Color, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
if opts.title != "" { if opts.ctx != nil || opts.title != "" {
hook, err := hookDialogTitle(opts.title) unhook, err := hookDialogTitle(opts.ctx, opts.title)
if hook == 0 { if err != nil {
return nil, err return nil, err
} }
defer unhookWindowsHookEx.Call(hook) defer unhook()
} }
s, _, _ := chooseColor.Call(uintptr(unsafe.Pointer(&args))) s, _, _ := chooseColor.Call(uintptr(unsafe.Pointer(&args)))
if opts.ctx != nil && opts.ctx.Err() != nil {
return nil, opts.ctx.Err()
}
if s == 0 { if s == 0 {
return nil, commDlgError() return nil, commDlgError()
} }

View file

@ -46,7 +46,18 @@ func selectFile(options []Option) (string, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() 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))) s, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args)))
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", opts.ctx.Err()
}
if s == 0 { if s == 0 {
return "", commDlgError() return "", commDlgError()
} }
@ -82,7 +93,18 @@ func selectFileMutiple(options []Option) ([]string, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() 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))) s, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args)))
if opts.ctx != nil && opts.ctx.Err() != nil {
return nil, opts.ctx.Err()
}
if s == 0 { if s == 0 {
return nil, commDlgError() return nil, commDlgError()
} }
@ -149,7 +171,18 @@ func selectFileSave(options []Option) (string, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() 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))) s, _, _ := getSaveFileName.Call(uintptr(unsafe.Pointer(&args)))
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", opts.ctx.Err()
}
if s == 0 { if s == 0 {
return "", commDlgError() return "", commDlgError()
} }

View file

@ -1,6 +1,7 @@
package zenity package zenity
import ( import (
"context"
"runtime" "runtime"
"syscall" "syscall"
"unsafe" "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() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
hook, err := hookMessageLabels(kind, opts) unhook, err := hookMessageLabels(kind, opts)
if hook == 0 { if err != nil {
return false, err return false, err
} }
defer unhookWindowsHookEx.Call(hook) defer unhook()
} }
s, _, err := messageBox.Call(0, s, _, err := messageBox.Call(0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(opts.title))), flags) uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(opts.title))), flags)
if opts.ctx != nil && opts.ctx.Err() != nil {
return false, opts.ctx.Err()
}
if s == 0 { if s == 0 {
return false, err return false, err
} }
@ -68,46 +72,35 @@ func message(kind messageKind, text string, options []Option) (bool, error) {
return false, nil return false, nil
} }
func hookMessageLabels(kind messageKind, opts options) (hook uintptr, err error) { func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFunc, err error) {
tid, _, _ := getCurrentThreadId.Call() return hookDialog(opts.ctx, func(wnd uintptr) {
hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET enumChildWindows.Call(wnd,
syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { syscall.NewCallback(func(wnd, lparam uintptr) uintptr {
if lparam.Message == 0x0110 { // WM_INITDIALOG name := [8]uint16{}
name := [7]uint16{} getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) if syscall.UTF16ToString(name[:]) == "Button" {
if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box ctl, _, _ := getDlgCtrlID.Call(wnd)
enumChildWindows.Call(lparam.Wnd, var text string
syscall.NewCallback(func(wnd, lparam uintptr) uintptr { switch ctl {
name := [7]uint16{} case 1, 6: // IDOK, IDYES
getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) text = opts.okLabel
if syscall.UTF16ToString(name[:]) == "Button" { case 2: // IDCANCEL
ctl, _, _ := getDlgCtrlID.Call(wnd) if kind == questionKind {
var text string text = opts.cancelLabel
switch ctl { } else if opts.extraButton != "" {
case 1, 6: // IDOK, IDYES text = opts.extraButton
text = opts.okLabel } else {
case 2: // IDCANCEL text = opts.okLabel
if kind == questionKind { }
text = opts.cancelLabel case 7: // IDNO
} else if opts.extraButton != "" { text = opts.extraButton
text = opts.extraButton }
} else { if text != "" {
text = opts.okLabel ptr := syscall.StringToUTF16Ptr(text)
} setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr)))
case 7: // IDNO }
text = opts.extraButton
}
if text != "" {
ptr := syscall.StringToUTF16Ptr(text)
setWindowText.Call(wnd, uintptr(unsafe.Pointer(ptr)))
}
}
return 1
}), 0)
} }
} return 1
next, _, _ := callNextHookEx.Call(hook, uintptr(code), wparam, uintptr(unsafe.Pointer(lparam))) }), 0)
return next })
}), 0, tid)
return
} }

View file

@ -1,7 +1,9 @@
package zenity package zenity
import ( import (
"context"
"fmt" "fmt"
"sync"
"syscall" "syscall"
"unsafe" "unsafe"
) )
@ -49,22 +51,73 @@ type _CWPRETSTRUCT struct {
Wnd uintptr 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() tid, _, _ := getCurrentThreadId.Call()
hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET
syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { syscall.NewCallback(func(code int, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr {
if lparam.Message == 0x0110 { // WM_INITDIALOG if lparam.Message == 0x0110 { // WM_INITDIALOG
name := [7]uint16{} name := [8]uint16{}
getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box
ptr := syscall.StringToUTF16Ptr(title) var close bool
setWindowText.Call(lparam.Wnd, uintptr(unsafe.Pointer(ptr)))
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))) next, _, _ := callNextHookEx.Call(hook, uintptr(code), wparam, uintptr(unsafe.Pointer(lparam)))
return next return next
}), 0, tid) }), 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{} type _COMObject struct{}

View file

@ -73,7 +73,7 @@ func Title(title string) Option {
// DialogIcon is the enumeration for dialog icons. // DialogIcon is the enumeration for dialog icons.
type DialogIcon int type DialogIcon int
// Icons for // The stock dialog icons.
const ( const (
ErrorIcon DialogIcon = iota + 1 ErrorIcon DialogIcon = iota + 1
WarningIcon WarningIcon
@ -87,6 +87,8 @@ func Icon(icon DialogIcon) Option {
} }
// Context returns an Option to set a Context that can dismiss the dialog. // 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 { func Context(ctx context.Context) Option {
return funcOption(func(o *options) { o.ctx = ctx }) return funcOption(func(o *options) { o.ctx = ctx })
} }