Windows improvements.
This commit is contained in:
parent
48a6137218
commit
521d58c99b
2 changed files with 223 additions and 26 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue