zenity/file_windows.go

459 lines
11 KiB
Go
Raw Normal View History

2019-12-29 20:00:50 -05:00
package zenity
2019-12-13 08:45:23 -05:00
import (
2022-05-03 09:20:22 -04:00
"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"
2022-06-17 22:45:49 -04:00
"github.com/ncruces/zenity/internal/win"
2022-05-19 18:39:21 -04:00
)
const (
_FOS_NOCHANGEDIR = 0x00000008
_FOS_PICKFOLDERS = 0x00000020
_FOS_FORCEFILESYSTEM = 0x00000040
_FOS_ALLOWMULTISELECT = 0x00000200
_FOS_FORCESHOWHIDDEN = 0x10000000
2022-06-19 21:14:08 -04:00
_SIGDN_FILESYSPATH = 0x80058000
2019-12-13 20:07:25 -05:00
)
2019-12-13 08:45:23 -05:00
2021-03-04 07:42:30 -05:00
func selectFile(opts options) (string, error) {
2020-01-09 20:46:53 -05:00
if opts.directory {
res, _, err := pickFolders(opts, false)
return res, err
}
2022-06-17 22:45:49 -04:00
var args win.OPENFILENAME
2019-12-13 08:45:23 -05:00
args.StructSize = uint32(unsafe.Sizeof(args))
2022-06-18 07:37:39 -04:00
args.Owner, _ = opts.attach.(win.HWND)
2022-06-17 22:45:49 -04:00
args.Flags = win.OFN_NOCHANGEDIR | win.OFN_FILEMUSTEXIST | win.OFN_EXPLORER
2019-12-13 08:45:23 -05:00
2021-03-03 22:25:45 -05:00
if opts.title != nil {
2022-06-20 11:05:11 -04:00
args.Title = strptr(*opts.title)
2019-12-13 08:45:23 -05:00
}
2020-01-24 07:52:45 -05:00
if opts.showHidden {
2022-06-17 22:45:49 -04:00
args.Flags |= win.OFN_FORCESHOWHIDDEN
}
2020-01-24 07:52:45 -05:00
if opts.fileFilters != nil {
args.Filter = &initFilters(opts.fileFilters)[0]
2020-01-08 19:54:34 -05:00
}
2019-12-13 08:45:23 -05:00
2021-04-27 20:27:28 -04:00
var res [32768]uint16
2019-12-13 08:45:23 -05:00
args.File = &res[0]
args.MaxFile = uint32(len(res))
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
2019-12-13 08:45:23 -05:00
2021-03-26 08:40:24 -04:00
defer setup()()
2020-01-02 20:44:23 -05:00
2020-01-30 09:14:42 -05:00
if opts.ctx != nil {
2022-05-19 18:39:21 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
2020-01-30 09:14:42 -05:00
if err != nil {
return "", err
}
defer unhook()
}
2022-06-17 22:45:49 -04:00
ok := win.GetOpenFileName(&args)
2020-01-30 09:14:42 -05:00
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", opts.ctx.Err()
}
2022-06-17 22:45:49 -04:00
if !ok {
return "", win.CommDlgError()
2019-12-28 10:34:38 -05:00
}
2019-12-13 08:45:23 -05:00
return syscall.UTF16ToString(res[:]), nil
}
2022-05-19 09:09:21 -04:00
func selectFileMultiple(opts options) ([]string, error) {
2020-01-09 20:46:53 -05:00
if opts.directory {
_, res, err := pickFolders(opts, true)
return res, err
}
2022-06-17 22:45:49 -04:00
var args win.OPENFILENAME
2019-12-13 08:45:23 -05:00
args.StructSize = uint32(unsafe.Sizeof(args))
2022-06-18 07:37:39 -04:00
args.Owner, _ = opts.attach.(win.HWND)
2022-06-17 22:45:49 -04:00
args.Flags = win.OFN_NOCHANGEDIR | win.OFN_ALLOWMULTISELECT | win.OFN_FILEMUSTEXIST | win.OFN_EXPLORER
2019-12-13 08:45:23 -05:00
2021-03-03 22:25:45 -05:00
if opts.title != nil {
2022-06-20 11:05:11 -04:00
args.Title = strptr(*opts.title)
2019-12-13 08:45:23 -05:00
}
2020-01-24 07:52:45 -05:00
if opts.showHidden {
2022-06-17 22:45:49 -04:00
args.Flags |= win.OFN_FORCESHOWHIDDEN
}
2020-01-24 07:52:45 -05:00
if opts.fileFilters != nil {
args.Filter = &initFilters(opts.fileFilters)[0]
2020-01-08 19:54:34 -05:00
}
2019-12-13 08:45:23 -05:00
2021-04-27 20:27:28 -04:00
var res [32768 + 1024*256]uint16
2019-12-13 08:45:23 -05:00
args.File = &res[0]
args.MaxFile = uint32(len(res))
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
2019-12-13 08:45:23 -05:00
2021-03-26 08:40:24 -04:00
defer setup()()
2020-01-02 20:44:23 -05:00
2020-01-30 09:14:42 -05:00
if opts.ctx != nil {
2022-05-19 18:39:21 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
2020-01-30 09:14:42 -05:00
if err != nil {
return nil, err
}
defer unhook()
}
2022-06-17 22:45:49 -04:00
ok := win.GetOpenFileName(&args)
2020-01-30 09:14:42 -05:00
if opts.ctx != nil && opts.ctx.Err() != nil {
return nil, opts.ctx.Err()
}
2022-06-17 22:45:49 -04:00
if !ok {
return nil, win.CommDlgError()
2019-12-28 10:34:38 -05:00
}
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
}
2021-03-04 07:42:30 -05:00
func selectFileSave(opts options) (string, error) {
if opts.directory {
res, _, err := pickFolders(opts, false)
return res, err
}
2020-01-09 20:46:53 -05:00
2022-06-17 22:45:49 -04:00
var args win.OPENFILENAME
2019-12-13 08:45:23 -05:00
args.StructSize = uint32(unsafe.Sizeof(args))
2022-06-18 07:37:39 -04:00
args.Owner, _ = opts.attach.(win.HWND)
2022-06-17 22:45:49 -04:00
args.Flags = win.OFN_NOCHANGEDIR | win.OFN_PATHMUSTEXIST | win.OFN_NOREADONLYRETURN | win.OFN_EXPLORER
2019-12-13 08:45:23 -05:00
2021-03-03 22:25:45 -05:00
if opts.title != nil {
2022-06-20 11:05:11 -04:00
args.Title = strptr(*opts.title)
2019-12-13 08:45:23 -05:00
}
2020-01-24 07:52:45 -05:00
if opts.confirmOverwrite {
2022-06-17 22:45:49 -04:00
args.Flags |= win.OFN_OVERWRITEPROMPT
2019-12-13 08:45:23 -05:00
}
2020-01-24 07:52:45 -05:00
if opts.confirmCreate {
2022-06-17 22:45:49 -04:00
args.Flags |= win.OFN_CREATEPROMPT
}
2020-01-24 07:52:45 -05:00
if opts.showHidden {
2022-06-17 22:45:49 -04:00
args.Flags |= win.OFN_FORCESHOWHIDDEN
}
2020-01-24 07:52:45 -05:00
if opts.fileFilters != nil {
args.Filter = &initFilters(opts.fileFilters)[0]
2020-01-08 19:54:34 -05:00
}
2019-12-13 08:45:23 -05:00
2021-04-27 20:27:28 -04:00
var res [32768]uint16
2019-12-13 08:45:23 -05:00
args.File = &res[0]
args.MaxFile = uint32(len(res))
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
2019-12-13 08:45:23 -05:00
2021-03-26 08:40:24 -04:00
defer setup()()
2020-01-02 20:44:23 -05:00
2020-01-30 09:14:42 -05:00
if opts.ctx != nil {
2022-05-19 18:39:21 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
2020-01-30 09:14:42 -05:00
if err != nil {
return "", err
}
defer unhook()
}
2022-06-17 22:45:49 -04:00
ok := win.GetSaveFileName(&args)
2020-01-30 09:14:42 -05:00
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", opts.ctx.Err()
}
2022-06-17 22:45:49 -04:00
if !ok {
return "", win.CommDlgError()
2019-12-28 10:34:38 -05:00
}
2019-12-13 08:45:23 -05:00
return syscall.UTF16ToString(res[:]), nil
}
2020-01-09 20:46:53 -05:00
func pickFolders(opts options, multi bool) (str string, lst []string, err error) {
2021-03-26 08:40:24 -04:00
defer setup()()
2020-01-02 20:44:23 -05:00
2022-06-19 21:14:08 -04:00
err = win.CoInitializeEx(0, win.COINIT_APARTMENTTHREADED|win.COINIT_DISABLE_OLE1DDE)
2022-06-17 22:45:49 -04:00
if err != win.RPC_E_CHANGED_MODE {
if err != nil {
return "", nil, err
2020-01-02 20:44:23 -05:00
}
2022-06-17 22:45:49 -04:00
defer win.CoUninitialize()
2019-12-28 10:34:38 -05:00
}
2020-01-02 20:44:23 -05:00
2019-12-28 10:34:38 -05:00
var dialog *_IFileOpenDialog
2022-06-17 22:45:49 -04:00
err = win.CoCreateInstance(
2022-06-19 21:14:08 -04:00
_CLSID_FileOpenDialog, nil, win.CLSCTX_ALL,
2022-06-17 22:45:49 -04:00
_IID_IFileOpenDialog, unsafe.Pointer(&dialog))
if err != nil {
2021-07-12 20:43:52 -04:00
if multi {
2022-05-03 09:20:22 -04:00
return "", nil, fmt.Errorf("%w: multiple directory", ErrUnsupported)
2021-07-12 20:43:52 -04:00
}
return browseForFolder(opts)
2019-12-28 10:34:38 -05:00
}
2021-03-10 09:49:09 -05:00
defer dialog.Call(dialog.Release)
2019-12-28 10:34:38 -05:00
2020-01-02 20:44:23 -05:00
var flgs int
2022-06-17 22:45:49 -04:00
hr, _, _ := dialog.Call(dialog.GetOptions, uintptr(unsafe.Pointer(&flgs)))
2020-01-02 20:44:23 -05:00
if int32(hr) < 0 {
2020-01-09 20:46:53 -05:00
return "", nil, syscall.Errno(hr)
}
2022-05-19 18:39:21 -04:00
flgs |= _FOS_NOCHANGEDIR | _FOS_PICKFOLDERS | _FOS_FORCEFILESYSTEM
2020-01-09 20:46:53 -05:00
if multi {
2022-05-19 18:39:21 -04:00
flgs |= _FOS_ALLOWMULTISELECT
2019-12-28 10:34:38 -05:00
}
2020-01-24 07:52:45 -05:00
if opts.showHidden {
2022-05-19 18:39:21 -04:00
flgs |= _FOS_FORCESHOWHIDDEN
}
2022-05-19 18:39:21 -04:00
hr, _, _ = dialog.Call(dialog.SetOptions, uintptr(flgs))
2020-01-02 20:44:23 -05:00
if int32(hr) < 0 {
2020-01-09 20:46:53 -05:00
return "", nil, syscall.Errno(hr)
2019-12-28 10:34:38 -05:00
}
2021-03-03 22:25:45 -05:00
if opts.title != nil {
2022-06-20 11:05:11 -04:00
ptr := strptr(*opts.title)
2021-03-10 09:49:09 -05:00
dialog.Call(dialog.SetTitle, uintptr(unsafe.Pointer(ptr)))
2019-12-28 10:34:38 -05:00
}
2020-01-02 20:44:23 -05:00
if opts.filename != "" {
2022-06-17 22:45:49 -04:00
var item *win.IShellItem
2022-06-20 11:05:11 -04:00
ptr := strptr(opts.filename)
2022-06-17 22:45:49 -04:00
win.SHCreateItemFromParsingName(ptr, nil, _IID_IShellItem, &item)
2019-12-28 10:34:38 -05:00
2021-03-07 20:12:32 -05:00
if int32(hr) >= 0 && item != nil {
2021-03-10 09:49:09 -05:00
dialog.Call(dialog.SetFolder, uintptr(unsafe.Pointer(item)))
item.Call(item.Release)
2019-12-28 10:34:38 -05:00
}
}
2020-01-30 20:31:54 -05:00
if opts.ctx != nil {
2022-05-19 18:39:21 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
2020-01-30 20:31:54 -05:00
if err != nil {
return "", nil, err
}
defer unhook()
}
2022-06-18 07:37:39 -04:00
owner, _ := opts.attach.(win.HWND)
hr, _, _ = dialog.Call(dialog.Show, uintptr(owner))
2020-01-30 20:31:54 -05:00
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", nil, opts.ctx.Err()
}
2022-06-19 21:14:08 -04:00
if hr == uintptr(win.E_CANCELED) {
2021-04-29 11:05:28 -04:00
return "", nil, ErrCanceled
2020-01-02 20:44:23 -05:00
}
if int32(hr) < 0 {
2020-01-09 20:46:53 -05:00
return "", nil, syscall.Errno(hr)
2019-12-28 10:34:38 -05:00
}
2022-06-17 22:45:49 -04:00
shellItemPath := func(obj *win.COMObject, trap uintptr, a ...uintptr) error {
var item *win.IShellItem
2020-01-09 20:46:53 -05:00
hr, _, _ := obj.Call(trap, append(a, uintptr(unsafe.Pointer(&item)))...)
if int32(hr) < 0 {
return syscall.Errno(hr)
}
2021-03-10 09:49:09 -05:00
defer item.Call(item.Release)
2019-12-28 10:34:38 -05:00
2022-06-21 09:27:19 -04:00
var ptr win.Pointer
2021-03-10 09:49:09 -05:00
hr, _, _ = item.Call(item.GetDisplayName,
2022-06-19 21:14:08 -04:00
_SIGDN_FILESYSPATH, uintptr(unsafe.Pointer(&ptr)))
2020-01-09 20:46:53 -05:00
if int32(hr) < 0 {
return syscall.Errno(hr)
}
2022-06-17 22:45:49 -04:00
defer win.CoTaskMemFree(ptr)
2020-01-09 20:46:53 -05:00
2021-01-05 10:21:18 -05:00
var res []uint16
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&res))
2022-06-17 22:45:49 -04:00
hdr.Data, hdr.Len, hdr.Cap = uintptr(ptr), 32768, 32768
2021-01-05 10:21:18 -05:00
str = syscall.UTF16ToString(res)
2020-01-09 20:46:53 -05:00
lst = append(lst, str)
return nil
2019-12-28 10:34:38 -05:00
}
2020-01-09 20:46:53 -05:00
if multi {
var items *_IShellItemArray
2021-03-10 09:49:09 -05:00
hr, _, _ = dialog.Call(dialog.GetResults, uintptr(unsafe.Pointer(&items)))
2020-01-09 20:46:53 -05:00
if int32(hr) < 0 {
return "", nil, syscall.Errno(hr)
}
2021-03-10 09:49:09 -05:00
defer items.Call(items.Release)
2020-01-09 20:46:53 -05:00
var count uint32
2021-03-10 09:49:09 -05:00
hr, _, _ = items.Call(items.GetCount, uintptr(unsafe.Pointer(&count)))
2020-01-09 20:46:53 -05:00
if int32(hr) < 0 {
return "", nil, syscall.Errno(hr)
}
for i := uintptr(0); i < uintptr(count) && err == nil; i++ {
2022-06-17 22:45:49 -04:00
err = shellItemPath(&items.COMObject, items.GetItemAt, i)
2020-01-09 20:46:53 -05:00
}
} else {
2022-06-17 22:45:49 -04:00
err = shellItemPath(&dialog.COMObject, dialog.GetResult)
2020-01-09 20:46:53 -05:00
}
return
2019-12-28 10:34:38 -05:00
}
func browseForFolder(opts options) (string, []string, error) {
2022-06-17 22:45:49 -04:00
var args win.BROWSEINFO
2022-06-18 07:37:39 -04:00
args.Owner, _ = opts.attach.(win.HWND)
2022-06-19 21:14:08 -04:00
args.Flags = win.BIF_RETURNONLYFSDIRS
2019-12-13 20:07:25 -05:00
2021-03-03 22:25:45 -05:00
if opts.title != nil {
2022-06-20 11:05:11 -04:00
args.Title = strptr(*opts.title)
}
if opts.filename != "" {
2022-06-20 11:05:11 -04:00
args.LParam = strptr(opts.filename)
args.CallbackFunc = syscall.NewCallback(browseForFolderCallback)
2019-12-13 20:07:25 -05:00
}
2020-01-30 20:31:54 -05:00
if opts.ctx != nil {
2022-05-19 18:39:21 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
2020-01-30 20:31:54 -05:00
if err != nil {
return "", nil, err
}
defer unhook()
}
2022-06-17 22:45:49 -04:00
ptr := win.SHBrowseForFolder(&args)
2020-01-30 20:31:54 -05:00
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", nil, opts.ctx.Err()
}
2022-06-21 09:27:19 -04:00
if ptr == nullptr {
2021-04-29 11:05:28 -04:00
return "", nil, ErrCanceled
2019-12-13 20:07:25 -05:00
}
2022-06-17 22:45:49 -04:00
defer win.CoTaskMemFree(ptr)
2019-12-13 20:07:25 -05:00
2021-04-27 20:27:28 -04:00
var res [32768]uint16
2022-06-17 22:45:49 -04:00
win.SHGetPathFromIDListEx(ptr, &res[0], len(res), 0)
2019-12-13 20:07:25 -05:00
2020-01-09 20:46:53 -05:00
str := syscall.UTF16ToString(res[:])
return str, []string{str}, nil
2019-12-13 08:45:23 -05:00
}
2022-06-18 19:48:38 -04:00
func browseForFolderCallback(wnd win.HWND, msg uint32, lparam, data uintptr) uintptr {
2022-06-19 21:14:08 -04:00
if msg == win.BFFM_INITIALIZED {
win.SendMessage(wnd, win.BFFM_SETSELECTION, 1, data)
}
return 0
}
func initDirNameExt(filename string, name []uint16) (dir *uint16, ext *uint16) {
2021-02-18 08:24:02 -05:00
d, n := splitDirAndName(filename)
e := filepath.Ext(n)
if n != "" {
2021-05-07 09:02:44 -04:00
copy(name, syscall.StringToUTF16(n))
2021-02-18 08:24:02 -05:00
}
if d != "" {
2022-06-20 11:05:11 -04:00
dir = strptr(d)
2021-02-18 08:24:02 -05:00
}
2021-02-22 14:04:55 -05:00
if len(e) > 1 {
2022-06-20 11:05:11 -04:00
ext = strptr(e[1:])
2020-01-09 12:05:43 -05:00
}
return
2020-01-09 12:05:43 -05:00
}
2021-04-13 08:28:26 -04:00
func initFilters(filters FileFilters) []uint16 {
filters.simplify()
2021-04-13 09:37:10 -04:00
filters.name()
2019-12-13 08:45:23 -05:00
var res []uint16
for _, f := range filters {
res = append(res, utf16.Encode([]rune(f.Name))...)
res = append(res, 0)
2020-01-08 19:54:34 -05:00
for _, p := range f.Patterns {
res = append(res, utf16.Encode([]rune(p))...)
2019-12-13 08:45:23 -05:00
res = append(res, uint16(';'))
}
res = append(res, 0)
}
if res != nil {
res = append(res, 0)
}
return res
}
2021-03-10 09:49:09 -05:00
// https://github.com/wine-mirror/wine/blob/master/include/shobjidl.idl
2019-12-28 10:34:38 -05:00
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 _IFileOpenDialog struct {
2022-06-17 22:45:49 -04:00
win.COMObject
2021-03-10 09:49:09 -05:00
*_IFileOpenDialogVtbl
2019-12-28 10:34:38 -05:00
}
2020-01-09 20:46:53 -05:00
type _IShellItemArray struct {
2022-06-17 22:45:49 -04:00
win.COMObject
2021-03-10 09:49:09 -05:00
*_IShellItemArrayVtbl
2020-01-09 20:46:53 -05:00
}
2019-12-28 10:34:38 -05:00
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
}
2020-01-09 20:46:53 -05:00
type _IShellItemArrayVtbl struct {
_IUnknownVtbl
BindToHandler uintptr
GetPropertyStore uintptr
GetPropertyDescriptionList uintptr
GetAttributes uintptr
GetCount uintptr
GetItemAt uintptr
EnumItems uintptr
}