zenity/dialog/dialog_windows.go

382 lines
9.5 KiB
Go
Raw Normal View History

2019-12-13 08:45:23 -05:00
package dialog
import (
2019-12-28 10:34:38 -05:00
"errors"
"fmt"
2019-12-13 08:45:23 -05:00
"path/filepath"
2019-12-28 10:34:38 -05:00
"reflect"
2019-12-13 08:45:23 -05:00
"syscall"
"unicode/utf16"
"unsafe"
)
2019-12-13 20:07:25 -05:00
var (
2019-12-28 10:34:38 -05:00
comdlg32 = syscall.NewLazyDLL("comdlg32.dll")
2019-12-13 20:07:25 -05:00
ole32 = syscall.NewLazyDLL("ole32.dll")
shell32 = syscall.NewLazyDLL("shell32.dll")
2019-12-28 10:34:38 -05:00
getOpenFileName = comdlg32.NewProc("GetOpenFileNameW")
getSaveFileName = comdlg32.NewProc("GetSaveFileNameW")
commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError")
coInitializeEx = ole32.NewProc("CoInitializeEx")
coUninitialize = ole32.NewProc("CoUninitialize")
coCreateInstance = ole32.NewProc("CoCreateInstance")
coTaskMemFree = ole32.NewProc("CoTaskMemFree")
shBrowseForFolder = shell32.NewProc("SHBrowseForFolderW")
shGetPathFromIDListEx = shell32.NewProc("SHGetPathFromIDListEx")
shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName")
2019-12-13 20:07:25 -05:00
)
2019-12-13 08:45:23 -05:00
2019-12-13 20:07:25 -05:00
func OpenFile(title, defaultPath string, filters []FileFilter) (string, error) {
2019-12-13 08:45:23 -05:00
var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args))
2019-12-28 10:34:38 -05:00
args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
2019-12-13 08:45:23 -05:00
if title != "" {
args.Title = syscall.StringToUTF16Ptr(title)
}
if defaultPath != "" {
args.InitialDir = syscall.StringToUTF16Ptr(defaultPath)
}
args.Filter = &windowsFilters(filters)[0]
2019-12-28 10:34:38 -05:00
res := [32768]uint16{}
2019-12-13 08:45:23 -05:00
args.File = &res[0]
args.MaxFile = uint32(len(res))
2019-12-28 10:34:38 -05:00
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)
}
}
2019-12-13 08:45:23 -05:00
return syscall.UTF16ToString(res[:]), nil
}
func OpenFiles(title, defaultPath string, filters []FileFilter) ([]string, error) {
var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args))
2019-12-28 10:34:38 -05:00
args.Flags = 0x80208 // OFN_NOCHANGEDIR|OFN_ALLOWMULTISELECT|OFN_EXPLORER
2019-12-13 08:45:23 -05:00
if title != "" {
args.Title = syscall.StringToUTF16Ptr(title)
}
if defaultPath != "" {
args.InitialDir = syscall.StringToUTF16Ptr(defaultPath)
}
args.Filter = &windowsFilters(filters)[0]
2019-12-28 10:34:38 -05:00
res := [32768 + 1024*256]uint16{}
2019-12-13 08:45:23 -05:00
args.File = &res[0]
args.MaxFile = uint32(len(res))
2019-12-28 10:34:38 -05:00
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)
}
}
2019-12-13 08:45:23 -05:00
var i int
var nul bool
var split []string
for j, p := range res {
if p == 0 {
if nul {
break
}
if i < j {
split = append(split, string(utf16.Decode(res[i:j])))
}
i = j + 1
nul = true
} else {
nul = false
}
}
if len := len(split) - 1; len > 0 {
base := split[0]
for i := 0; i < len; i++ {
split[i] = filepath.Join(base, string(split[i+1]))
}
split = split[:len]
}
return split, nil
}
func SaveFile(title, defaultPath string, confirmOverwrite bool, filters []FileFilter) (string, error) {
var args _OPENFILENAME
args.StructSize = uint32(unsafe.Sizeof(args))
2019-12-28 10:34:38 -05:00
args.Flags = 0x80008 // OFN_NOCHANGEDIR|OFN_EXPLORER
2019-12-13 08:45:23 -05:00
if title != "" {
args.Title = syscall.StringToUTF16Ptr(title)
}
if defaultPath != "" {
args.InitialDir = syscall.StringToUTF16Ptr(defaultPath)
}
if confirmOverwrite {
2019-12-28 10:34:38 -05:00
args.Flags |= 0x2 // OFN_OVERWRITEPROMPT
2019-12-13 08:45:23 -05:00
}
args.Filter = &windowsFilters(filters)[0]
2019-12-28 10:34:38 -05:00
res := [32768]uint16{}
2019-12-13 08:45:23 -05:00
args.File = &res[0]
args.MaxFile = uint32(len(res))
2019-12-28 10:34:38 -05:00
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)
}
}
2019-12-13 08:45:23 -05:00
return syscall.UTF16ToString(res[:]), nil
}
func PickFolder(title, defaultPath string) (string, error) {
2019-12-28 10:34:38 -05:00
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) {
2019-12-13 20:07:25 -05:00
var args _BROWSEINFO
2019-12-28 10:34:38 -05:00
args.Flags = 0x1 // BIF_RETURNONLYFSDIRS
2019-12-13 20:07:25 -05:00
if title != "" {
args.Title = syscall.StringToUTF16Ptr(title)
}
2019-12-28 10:34:38 -05:00
ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args)))
2019-12-13 20:07:25 -05:00
if ptr == 0 {
return "", nil
}
2019-12-28 10:34:38 -05:00
defer coTaskMemFree.Call(ptr)
2019-12-13 20:07:25 -05:00
2019-12-28 10:34:38 -05:00
res := [32768]uint16{}
shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0)
2019-12-13 20:07:25 -05:00
return syscall.UTF16ToString(res[:]), nil
2019-12-13 08:45:23 -05:00
}
func windowsFilters(filters []FileFilter) []uint16 {
var res []uint16
for _, f := range filters {
res = append(res, utf16.Encode([]rune(f.Name))...)
res = append(res, 0)
for _, e := range f.Exts {
res = append(res, uint16('*'))
res = append(res, utf16.Encode([]rune(e))...)
res = append(res, uint16(';'))
}
res = append(res, 0)
}
if res != nil {
res = append(res, 0)
}
return res
}
type _OPENFILENAME struct {
StructSize uint32
Owner uintptr
Instance uintptr
Filter *uint16
CustomFilter *uint16
MaxCustomFilter uint32
FilterIndex uint32
File *uint16
MaxFile uint32
FileTitle *uint16
MaxFileTitle uint32
InitialDir *uint16
Title *uint16
Flags uint32
FileOffset uint16
FileExtension uint16
DefExt *uint16
CustData uintptr
FnHook uintptr
TemplateName *uint16
2019-12-13 20:07:25 -05:00
PvReserved uintptr
2019-12-13 08:45:23 -05:00
DwReserved uint32
FlagsEx uint32
}
2019-12-13 20:07:25 -05:00
type _BROWSEINFO struct {
Owner uintptr
2019-12-28 10:34:38 -05:00
Root uintptr
2019-12-13 20:07:25 -05:00
DisplayName *uint16
Title *uint16
Flags uint32
CallbackFunc uintptr
LParam uintptr
Image int32
}
2019-12-28 10:34:38 -05:00
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
}