Folder selection improvements, refactoring (windows).

This commit is contained in:
Nuno Cruces 2020-01-12 18:55:10 +00:00
parent 58dae10a83
commit f68fa5b0ba
5 changed files with 89 additions and 74 deletions

View file

@ -1,7 +1,6 @@
package zenity package zenity
import ( import (
"fmt"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
@ -13,7 +12,6 @@ import (
var ( var (
getOpenFileName = comdlg32.NewProc("GetOpenFileNameW") getOpenFileName = comdlg32.NewProc("GetOpenFileNameW")
getSaveFileName = comdlg32.NewProc("GetSaveFileNameW") getSaveFileName = comdlg32.NewProc("GetSaveFileNameW")
commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError")
shBrowseForFolder = shell32.NewProc("SHBrowseForFolderW") shBrowseForFolder = shell32.NewProc("SHBrowseForFolderW")
shGetPathFromIDListEx = shell32.NewProc("SHGetPathFromIDListEx") shGetPathFromIDListEx = shell32.NewProc("SHGetPathFromIDListEx")
shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName") shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName")
@ -175,7 +173,7 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
_CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL _CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL
_IID_IFileOpenDialog, uintptr(unsafe.Pointer(&dialog))) _IID_IFileOpenDialog, uintptr(unsafe.Pointer(&dialog)))
if int32(hr) < 0 { if int32(hr) < 0 {
return browseForFolder(opts.title) return browseForFolder(opts)
} }
defer dialog.Call(dialog.vtbl.Release) defer dialog.Call(dialog.vtbl.Release)
@ -267,12 +265,22 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
return return
} }
func browseForFolder(title string) (string, []string, error) { func browseForFolder(opts options) (string, []string, error) {
var args _BROWSEINFO var args _BROWSEINFO
args.Flags = 0x1 // BIF_RETURNONLYFSDIRS args.Flags = 0x1 // BIF_RETURNONLYFSDIRS
if title != "" { if opts.title != "" {
args.Title = syscall.StringToUTF16Ptr(title) args.Title = syscall.StringToUTF16Ptr(opts.title)
}
if opts.filename != "" {
ptr := syscall.StringToUTF16Ptr(opts.filename)
args.LParam = uintptr(unsafe.Pointer(ptr))
args.CallbackFunc = syscall.NewCallback(func(hwnd uintptr, msg uint32, lparam, data uintptr) uintptr {
if msg == 1 { // BFFM_INITIALIZED
sendMessage.Call(hwnd, 1024+103 /* BFFM_SETSELECTIONW */, 1 /* TRUE */, data)
}
return 0
})
} }
ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args))) ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args)))
@ -322,15 +330,6 @@ func windowsFilters(filters []FileFilter) []uint16 {
return res return res
} }
func commDlgError() error {
n, _, _ := commDlgExtendedError.Call()
if n == 0 {
return nil
} else {
return fmt.Errorf("Common Dialog error: %x", n)
}
}
type _OPENFILENAME struct { type _OPENFILENAME struct {
StructSize uint32 StructSize uint32
Owner uintptr Owner uintptr
@ -378,23 +377,6 @@ var (
_CLSID_FileOpenDialog = uuid("\x9c\x5a\x1c\xdc\x8a\xe8\xde\x4d\xa5\xa1\x60\xf8\x2a\x20\xae\xf7") _CLSID_FileOpenDialog = uuid("\x9c\x5a\x1c\xdc\x8a\xe8\xde\x4d\xa5\xa1\x60\xf8\x2a\x20\xae\xf7")
) )
type _COMObject struct{}
func (o *_COMObject) Call(trap uintptr, a ...uintptr) (r1, r2 uintptr, lastErr error) {
self := uintptr(unsafe.Pointer(o))
nargs := uintptr(len(a))
switch nargs {
case 0:
return syscall.Syscall(trap, nargs+1, self, 0, 0)
case 1:
return syscall.Syscall(trap, nargs+1, self, a[0], 0)
case 2:
return syscall.Syscall(trap, nargs+1, self, a[0], a[1])
default:
panic("COM call with too many arguments.")
}
}
type _IFileOpenDialog struct { type _IFileOpenDialog struct {
_COMObject _COMObject
vtbl *_IFileOpenDialogVtbl vtbl *_IFileOpenDialogVtbl
@ -467,9 +449,3 @@ type _IShellItemArrayVtbl struct {
GetItemAt uintptr GetItemAt uintptr
EnumItems uintptr EnumItems uintptr
} }
type _IUnknownVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
}

View file

@ -1,6 +1,10 @@
package zenity package zenity
import "syscall" import (
"fmt"
"syscall"
"unsafe"
)
var ( var (
comdlg32 = syscall.NewLazyDLL("comdlg32.dll") comdlg32 = syscall.NewLazyDLL("comdlg32.dll")
@ -9,6 +13,8 @@ var (
shell32 = syscall.NewLazyDLL("shell32.dll") shell32 = syscall.NewLazyDLL("shell32.dll")
user32 = syscall.NewLazyDLL("user32.dll") user32 = syscall.NewLazyDLL("user32.dll")
commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError")
getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
coInitializeEx = ole32.NewProc("CoInitializeEx") coInitializeEx = ole32.NewProc("CoInitializeEx")
@ -16,6 +22,7 @@ var (
coCreateInstance = ole32.NewProc("CoCreateInstance") coCreateInstance = ole32.NewProc("CoCreateInstance")
coTaskMemFree = ole32.NewProc("CoTaskMemFree") coTaskMemFree = ole32.NewProc("CoTaskMemFree")
sendMessage = user32.NewProc("SendMessageW")
getClassName = user32.NewProc("GetClassNameA") getClassName = user32.NewProc("GetClassNameA")
setWindowsHookEx = user32.NewProc("SetWindowsHookExW") setWindowsHookEx = user32.NewProc("SetWindowsHookExW")
unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx") unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
@ -25,6 +32,15 @@ var (
setWindowText = user32.NewProc("SetWindowTextW") setWindowText = user32.NewProc("SetWindowTextW")
) )
func commDlgError() error {
n, _, _ := commDlgExtendedError.Call()
if n == 0 {
return nil
} else {
return fmt.Errorf("Common Dialog error: %x", n)
}
}
type _CWPRETSTRUCT struct { type _CWPRETSTRUCT struct {
Result uintptr Result uintptr
LParam uintptr LParam uintptr
@ -32,3 +48,26 @@ type _CWPRETSTRUCT struct {
Message uint32 Message uint32
HWnd uintptr HWnd uintptr
} }
type _COMObject struct{}
func (o *_COMObject) Call(trap uintptr, a ...uintptr) (r1, r2 uintptr, lastErr error) {
self := uintptr(unsafe.Pointer(o))
nargs := uintptr(len(a))
switch nargs {
case 0:
return syscall.Syscall(trap, nargs+1, self, 0, 0)
case 1:
return syscall.Syscall(trap, nargs+1, self, a[0], 0)
case 2:
return syscall.Syscall(trap, nargs+1, self, a[0], a[1])
default:
panic("COM call with too many arguments.")
}
}
type _IUnknownVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
}

View file

@ -6,7 +6,7 @@ import (
"github.com/ncruces/zenity/internal/osa" "github.com/ncruces/zenity/internal/osa"
) )
func Error(text string, options ...Option) (bool, error) { func Question(text string, options ...Option) (bool, error) {
return message(0, text, options) return message(0, text, options)
} }
@ -14,18 +14,18 @@ func Info(text string, options ...Option) (bool, error) {
return message(1, text, options) return message(1, text, options)
} }
func Question(text string, options ...Option) (bool, error) { func Warning(text string, options ...Option) (bool, error) {
return message(2, text, options) return message(2, text, options)
} }
func Warning(text string, options ...Option) (bool, error) { func Error(text string, options ...Option) (bool, error) {
return message(3, text, options) return message(3, text, options)
} }
func message(typ int, text string, options []Option) (bool, error) { func message(typ int, text string, options []Option) (bool, error) {
opts := optsParse(options) opts := optsParse(options)
data := osa.Msg{Text: text} data := osa.Msg{Text: text}
dialog := typ == 2 || opts.icon != 0 dialog := typ == 0 || opts.icon != 0
if dialog { if dialog {
data.Operation = "displayDialog" data.Operation = "displayDialog"
@ -47,16 +47,16 @@ func message(typ int, text string, options []Option) (bool, error) {
} }
switch typ { switch typ {
case 0:
data.As = "critical"
case 1: case 1:
data.As = "informational" data.As = "informational"
case 3: case 2:
data.As = "warning" data.As = "warning"
case 3:
data.As = "critical"
} }
} }
if typ != 2 { if typ != 0 {
if dialog { if dialog {
opts.ok = "OK" opts.ok = "OK"
} }
@ -69,7 +69,7 @@ func message(typ int, text string, options []Option) (bool, error) {
if opts.cancel == "" { if opts.cancel == "" {
opts.cancel = "Cancel" opts.cancel = "Cancel"
} }
if typ == 2 { if typ == 0 {
if opts.extra == "" { if opts.extra == "" {
data.Buttons = []string{opts.cancel, opts.ok} data.Buttons = []string{opts.cancel, opts.ok}
data.Default = 2 data.Default = 2

View file

@ -8,24 +8,6 @@ import (
"github.com/ncruces/zenity/internal/zen" "github.com/ncruces/zenity/internal/zen"
) )
// Display error dialog.
//
// Returns true on OK, false on dismiss, or ErrExtraButton.
//
// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize.
func Error(text string, options ...Option) (bool, error) {
return message("--error", text, options)
}
// Display info dialog.
//
// Returns true on OK, false on dismiss, or ErrExtraButton.
//
// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize.
func Info(text string, options ...Option) (bool, error) {
return message("--info", text, options)
}
// Display question dialog. // Display question dialog.
// //
// Returns true on OK, false on Cancel, or ErrExtraButton. // Returns true on OK, false on Cancel, or ErrExtraButton.
@ -36,6 +18,15 @@ func Question(text string, options ...Option) (bool, error) {
return message("--question", text, options) return message("--question", text, options)
} }
// Display info dialog.
//
// Returns true on OK, false on dismiss, or ErrExtraButton.
//
// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize.
func Info(text string, options ...Option) (bool, error) {
return message("--info", text, options)
}
// Display warning dialog. // Display warning dialog.
// //
// Returns true on OK, false on dismiss, or ErrExtraButton. // Returns true on OK, false on dismiss, or ErrExtraButton.
@ -45,6 +36,15 @@ func Warning(text string, options ...Option) (bool, error) {
return message("--warning", text, options) return message("--warning", text, options)
} }
// Display error dialog.
//
// Returns true on OK, false on dismiss, or ErrExtraButton.
//
// Valid options: Title, Icon, OKLabel, ExtraButton, NoWrap, Ellipsize.
func Error(text string, options ...Option) (bool, error) {
return message("--error", text, options)
}
func message(arg, text string, options []Option) (bool, error) { func message(arg, text string, options []Option) (bool, error) {
opts := optsParse(options) opts := optsParse(options)

View file

@ -10,7 +10,7 @@ var (
messageBox = user32.NewProc("MessageBoxW") messageBox = user32.NewProc("MessageBoxW")
) )
func Error(text string, options ...Option) (bool, error) { func Question(text string, options ...Option) (bool, error) {
return message(0, text, options) return message(0, text, options)
} }
@ -18,11 +18,11 @@ func Info(text string, options ...Option) (bool, error) {
return message(1, text, options) return message(1, text, options)
} }
func Question(text string, options ...Option) (bool, error) { func Warning(text string, options ...Option) (bool, error) {
return message(2, text, options) return message(2, text, options)
} }
func Warning(text string, options ...Option) (bool, error) { func Error(text string, options ...Option) (bool, error) {
return message(3, text, options) return message(3, text, options)
} }
@ -32,9 +32,9 @@ func message(typ int, text string, options []Option) (bool, error) {
var flags, caption uintptr var flags, caption uintptr
switch { switch {
case typ == 2 && opts.extra != "": case typ == 0 && opts.extra != "":
flags |= 0x3 // MB_YESNOCANCEL flags |= 0x3 // MB_YESNOCANCEL
case typ == 2 || opts.extra != "": case typ == 0 || opts.extra != "":
flags |= 0x1 // MB_OKCANCEL flags |= 0x1 // MB_OKCANCEL
} }
@ -49,7 +49,7 @@ func message(typ int, text string, options []Option) (bool, error) {
flags |= 0x40 // MB_ICONINFORMATION flags |= 0x40 // MB_ICONINFORMATION
} }
if typ == 2 && opts.defcancel { if typ == 0 && opts.defcancel {
if opts.extra == "" { if opts.extra == "" {
flags |= 0x100 // MB_DEFBUTTON2 flags |= 0x100 // MB_DEFBUTTON2
} else { } else {
@ -79,7 +79,7 @@ func message(typ int, text string, options []Option) (bool, error) {
if n == 0 { if n == 0 {
return false, err return false, err
} }
if n == 7 || n == 2 && typ != 2 { // IDNO if n == 7 || n == 2 && typ != 0 { // IDNO
return false, ErrExtraButton return false, ErrExtraButton
} }
if n == 1 || n == 6 { // IDOK, IDYES if n == 1 || n == 6 { // IDOK, IDYES
@ -107,7 +107,7 @@ func hookMessageLabels(typ int, opts options) (hook uintptr, err error) {
case 1, 6: // IDOK, IDYES case 1, 6: // IDOK, IDYES
text = opts.ok text = opts.ok
case 2: // IDCANCEL case 2: // IDCANCEL
if typ == 2 { if typ == 0 {
text = opts.cancel text = opts.cancel
} else if opts.extra != "" { } else if opts.extra != "" {
text = opts.extra text = opts.extra