Windows improvements.

This commit is contained in:
Nuno Cruces 2019-12-28 15:34:38 +00:00
parent 48a6137218
commit 521d58c99b
2 changed files with 223 additions and 26 deletions

View file

@ -2,8 +2,10 @@ package dialog
import "testing" import "testing"
const defaultPath = ""
func TestOpenFile(t *testing.T) { func TestOpenFile(t *testing.T) {
res, err := OpenFile("", "", []FileFilter{ res, err := OpenFile("", defaultPath, []FileFilter{
{"Go files", []string{".go"}}, {"Go files", []string{".go"}},
{"Web files", []string{".html", ".js", ".css"}}, {"Web files", []string{".html", ".js", ".css"}},
{"Image files", []string{".png", ".gif", ".ico", ".jpg", ".webp"}}, {"Image files", []string{".png", ".gif", ".ico", ".jpg", ".webp"}},
@ -17,7 +19,7 @@ func TestOpenFile(t *testing.T) {
} }
func TestOpenFiles(t *testing.T) { func TestOpenFiles(t *testing.T) {
res, err := OpenFiles("", "", []FileFilter{ res, err := OpenFiles("", defaultPath, []FileFilter{
{"Go files", []string{".go"}}, {"Go files", []string{".go"}},
{"Web files", []string{".html", ".js", ".css"}}, {"Web files", []string{".html", ".js", ".css"}},
{"Image files", []string{".png", ".gif", ".ico", ".jpg", ".webp"}}, {"Image files", []string{".png", ".gif", ".ico", ".jpg", ".webp"}},
@ -31,7 +33,7 @@ func TestOpenFiles(t *testing.T) {
} }
func TestSaveFile(t *testing.T) { func TestSaveFile(t *testing.T) {
res, err := SaveFile("", "", true, []FileFilter{ res, err := SaveFile("", defaultPath, true, []FileFilter{
{"Go files", []string{".go"}}, {"Go files", []string{".go"}},
{"Web files", []string{".html", ".js", ".css"}}, {"Web files", []string{".html", ".js", ".css"}},
{"Image files", []string{".png", ".gif", ".ico", ".jpg", ".webp"}}, {"Image files", []string{".png", ".gif", ".ico", ".jpg", ".webp"}},
@ -45,7 +47,7 @@ func TestSaveFile(t *testing.T) {
} }
func TestPickFolder(t *testing.T) { func TestPickFolder(t *testing.T) {
res, err := PickFolder("", "") res, err := PickFolder("", defaultPath)
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View file

@ -1,28 +1,36 @@
package dialog package dialog
import ( import (
"errors"
"fmt"
"path/filepath" "path/filepath"
"reflect"
"syscall" "syscall"
"unicode/utf16" "unicode/utf16"
"unsafe" "unsafe"
) )
var ( var (
comdlg32 = syscall.NewLazyDLL("comdlg32.dll")
ole32 = syscall.NewLazyDLL("ole32.dll") ole32 = syscall.NewLazyDLL("ole32.dll")
shell32 = syscall.NewLazyDLL("shell32.dll") shell32 = syscall.NewLazyDLL("shell32.dll")
comdlg32 = syscall.NewLazyDLL("comdlg32.dll")
coTaskMemFree = ole32.NewProc("CoTaskMemFree") getOpenFileName = comdlg32.NewProc("GetOpenFileNameW")
getOpenFileName = comdlg32.NewProc("GetOpenFileNameW") getSaveFileName = comdlg32.NewProc("GetSaveFileNameW")
getSaveFileName = comdlg32.NewProc("GetSaveFileNameW") commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError")
browseForFolder = shell32.NewProc("SHBrowseForFolderW") coInitializeEx = ole32.NewProc("CoInitializeEx")
getPathFromIDListEx = shell32.NewProc("SHGetPathFromIDListEx") coUninitialize = ole32.NewProc("CoUninitialize")
coCreateInstance = ole32.NewProc("CoCreateInstance")
coTaskMemFree = ole32.NewProc("CoTaskMemFree")
shBrowseForFolder = shell32.NewProc("SHBrowseForFolderW")
shGetPathFromIDListEx = shell32.NewProc("SHGetPathFromIDListEx")
shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName")
) )
func OpenFile(title, defaultPath string, filters []FileFilter) (string, error) { func OpenFile(title, defaultPath string, filters []FileFilter) (string, error) {
var args _OPENFILENAME var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args)) args.StructSize = uint32(unsafe.Sizeof(args))
args.Flags = 0x00080008 // OFN_NOCHANGEDIR|OFN_EXPLORER args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
if title != "" { if title != "" {
args.Title = syscall.StringToUTF16Ptr(title) args.Title = syscall.StringToUTF16Ptr(title)
@ -32,18 +40,26 @@ func OpenFile(title, defaultPath string, filters []FileFilter) (string, error) {
} }
args.Filter = &windowsFilters(filters)[0] args.Filter = &windowsFilters(filters)[0]
res := [1024]uint16{} res := [32768]uint16{}
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
_, _, _ = getOpenFileName.Call(uintptr(unsafe.Pointer(&args))) n, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args)))
if n == 0 {
n, _, _ = commDlgExtendedError.Call()
if n == 0 {
return "", nil
} else {
return "", fmt.Errorf("Common Dialog error: %x", n)
}
}
return syscall.UTF16ToString(res[:]), nil return syscall.UTF16ToString(res[:]), nil
} }
func OpenFiles(title, defaultPath string, filters []FileFilter) ([]string, error) { func OpenFiles(title, defaultPath string, filters []FileFilter) ([]string, error) {
var args _OPENFILENAME var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args)) args.StructSize = uint32(unsafe.Sizeof(args))
args.Flags = 0x00080208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_EXPLORER args.Flags = 0x80208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_EXPLORER
if title != "" { if title != "" {
args.Title = syscall.StringToUTF16Ptr(title) args.Title = syscall.StringToUTF16Ptr(title)
@ -53,11 +69,19 @@ func OpenFiles(title, defaultPath string, filters []FileFilter) ([]string, error
} }
args.Filter = &windowsFilters(filters)[0] args.Filter = &windowsFilters(filters)[0]
res := [65536]uint16{} res := [32768 + 1024*256]uint16{}
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
_, _, _ = getOpenFileName.Call(uintptr(unsafe.Pointer(&args))) n, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args)))
if n == 0 {
n, _, _ = commDlgExtendedError.Call()
if n == 0 {
return nil, nil
} else {
return nil, fmt.Errorf("Common Dialog error: %x", n)
}
}
var i int var i int
var nul bool var nul bool
@ -89,7 +113,7 @@ func OpenFiles(title, defaultPath string, filters []FileFilter) ([]string, error
func SaveFile(title, defaultPath string, confirmOverwrite bool, filters []FileFilter) (string, error) { func SaveFile(title, defaultPath string, confirmOverwrite bool, filters []FileFilter) (string, error) {
var args _OPENFILENAME var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args)) args.StructSize = uint32(unsafe.Sizeof(args))
args.Flags = 0x00080008 // OFN_NOCHANGEDIR|OFN_EXPLORER args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
if title != "" { if title != "" {
args.Title = syscall.StringToUTF16Ptr(title) args.Title = syscall.StringToUTF16Ptr(title)
@ -98,34 +122,115 @@ func SaveFile(title, defaultPath string, confirmOverwrite bool, filters []FileFi
args.InitialDir = syscall.StringToUTF16Ptr(defaultPath) args.InitialDir = syscall.StringToUTF16Ptr(defaultPath)
} }
if confirmOverwrite { if confirmOverwrite {
args.Flags |= 0x00000002 // OFN_OVERWRITEPROMPT args.Flags |= 0x2 // OFN_OVERWRITEPROMPT
} }
args.Filter = &windowsFilters(filters)[0] args.Filter = &windowsFilters(filters)[0]
res := [1024]uint16{} res := [32768]uint16{}
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
_, _, _ = getSaveFileName.Call(uintptr(unsafe.Pointer(&args))) n, _, _ := getSaveFileName.Call(uintptr(unsafe.Pointer(&args)))
if n == 0 {
n, _, _ = commDlgExtendedError.Call()
if n == 0 {
return "", nil
} else {
return "", fmt.Errorf("Common Dialog error: %x", n)
}
}
return syscall.UTF16ToString(res[:]), nil return syscall.UTF16ToString(res[:]), nil
} }
func PickFolder(title, defaultPath string) (string, error) { func PickFolder(title, defaultPath string) (string, error) {
hr, _, _ := coInitializeEx.Call(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE
if hr < 0 {
return "", errors.New("COM initialization failed.")
}
defer coUninitialize.Call()
var dialog *_IFileOpenDialog
hr, _, _ = coCreateInstance.Call(
_CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL
_IID_IFileOpenDialog, uintptr(unsafe.Pointer(&dialog)))
if hr < 0 || dialog == nil {
return browseForFolder(title, defaultPath)
}
defer dialog.Call(dialog.vtbl.Release)
var opts int
hr, _, _ = dialog.Call(dialog.vtbl.GetOptions, uintptr(unsafe.Pointer(&opts)))
if hr < 0 {
return "", fmt.Errorf("IFileOpenDialog.GetOptions error: %x", hr)
}
hr, _, _ = dialog.Call(dialog.vtbl.SetOptions, uintptr(opts|0x68)) // FOS_NOCHANGEDIR|FOS_PICKFOLDERS|FOS_FORCEFILESYSTEM
if hr < 0 {
return "", fmt.Errorf("IFileOpenDialog.SetOptions error: %x", hr)
}
if title != "" {
ptr := syscall.StringToUTF16Ptr(title)
dialog.Call(dialog.vtbl.SetTitle, uintptr(unsafe.Pointer(ptr)))
}
if defaultPath != "" {
var item *_IShellItem
ptr := syscall.StringToUTF16Ptr(defaultPath)
hr, _, _ = shCreateItemFromParsingName.Call(
uintptr(unsafe.Pointer(ptr)), 0,
_IID_IShellItem,
uintptr(unsafe.Pointer(&item)))
if hr >= 0 && item != nil {
dialog.Call(dialog.vtbl.SetDefaultFolder, uintptr(unsafe.Pointer(item)))
item.Call(item.vtbl.Release)
}
}
hr, _, _ = dialog.Call(dialog.vtbl.Show, 0)
if hr < 0 {
return "", fmt.Errorf("IFileOpenDialog.Show error: %x", hr)
}
var item *_IShellItem
hr, _, _ = dialog.Call(dialog.vtbl.GetResult, uintptr(unsafe.Pointer(&item)))
if hr < 0 {
return "", fmt.Errorf("IFileOpenDialog.GetResult error: %x", hr)
}
if item == nil {
return "", nil
}
defer item.Call(item.vtbl.Release)
var ptr uintptr
hr, _, _ = item.Call(item.vtbl.GetDisplayName,
0x80058000, // SIGDN_FILESYSPATH
uintptr(unsafe.Pointer(&ptr)))
if hr < 0 {
return "", fmt.Errorf("IShellItem.GetDisplayName error: %x", hr)
}
defer coTaskMemFree.Call(ptr)
res := reflect.SliceHeader{Data: ptr, Len: 32768, Cap: 32768}
return syscall.UTF16ToString(*(*[]uint16)(unsafe.Pointer(&res))), nil
}
func browseForFolder(title, defaultPath string) (string, error) {
var args _BROWSEINFO var args _BROWSEINFO
args.Flags = 0x00000051 // BIF_RETURNONLYFSDIRS|BIF_USENEWUI args.Flags = 0x1 // BIF_RETURNONLYFSDIRS
if title != "" { if title != "" {
args.Title = syscall.StringToUTF16Ptr(title) args.Title = syscall.StringToUTF16Ptr(title)
} }
ptr, _, _ := browseForFolder.Call(uintptr(unsafe.Pointer(&args))) ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args)))
if ptr == 0 { if ptr == 0 {
return "", nil return "", nil
} }
defer coTaskMemFree.Call(ptr)
res := [1024]uint16{} res := [32768]uint16{}
_, _, _ = getPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0) shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0)
_, _, _ = coTaskMemFree.Call(ptr)
return syscall.UTF16ToString(res[:]), nil return syscall.UTF16ToString(res[:]), nil
} }
@ -176,7 +281,7 @@ type _OPENFILENAME struct {
type _BROWSEINFO struct { type _BROWSEINFO struct {
Owner uintptr Owner uintptr
Root *uint16 Root uintptr
DisplayName *uint16 DisplayName *uint16
Title *uint16 Title *uint16
Flags uint32 Flags uint32
@ -184,3 +289,93 @@ type _BROWSEINFO struct {
LParam uintptr LParam uintptr
Image int32 Image int32
} }
func uuid(s string) uintptr {
return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
}
var (
_IID_IShellItem = uuid("\x1e\x6d\x82\x43\x18\xe7\xee\x42\xbc\x55\xa1\xe2\x61\xc3\x7b\xfe")
_IID_IFileOpenDialog = uuid("\x88\x72\x7c\xd5\xad\xd4\x68\x47\xbe\x02\x9d\x96\x95\x32\xd9\x60")
_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 {
_COMObject
vtbl *_IFileOpenDialogVtbl
}
type _IShellItem struct {
_COMObject
vtbl *_IShellItemVtbl
}
type _IFileOpenDialogVtbl struct {
_IFileDialogVtbl
GetResults uintptr
GetSelectedItems uintptr
}
type _IFileDialogVtbl struct {
_IModalWindowVtbl
SetFileTypes uintptr
SetFileTypeIndex uintptr
GetFileTypeIndex uintptr
Advise uintptr
Unadvise uintptr
SetOptions uintptr
GetOptions uintptr
SetDefaultFolder uintptr
SetFolder uintptr
GetFolder uintptr
GetCurrentSelection uintptr
SetFileName uintptr
GetFileName uintptr
SetTitle uintptr
SetOkButtonLabel uintptr
SetFileNameLabel uintptr
GetResult uintptr
AddPlace uintptr
SetDefaultExtension uintptr
Close uintptr
SetClientGuid uintptr
ClearClientData uintptr
SetFilter uintptr
}
type _IModalWindowVtbl struct {
_IUnknownVtbl
Show uintptr
}
type _IShellItemVtbl struct {
_IUnknownVtbl
BindToHandler uintptr
GetParent uintptr
GetDisplayName uintptr
GetAttributes uintptr
Compare uintptr
}
type _IUnknownVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
}