zenity/file_windows.go

360 lines
7.8 KiB
Go
Raw Permalink 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"
"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
)
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 {
2022-12-15 06:26:08 -05:00
args.Filter = initFilters(opts.fileFilters)
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
2022-06-28 09:52:02 -04:00
defer setup(args.Owner)()
2022-06-23 09:49:08 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
if err != nil {
return "", err
2020-01-30 09:14:42 -05:00
}
2022-06-23 09:49:08 -04:00
defer unhook()
2020-01-30 09:14:42 -05:00
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 {
2022-12-15 06:26:08 -05:00
args.Filter = initFilters(opts.fileFilters)
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
2022-06-28 09:52:02 -04:00
defer setup(args.Owner)()
2022-06-23 09:49:08 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
if err != nil {
return nil, err
2020-01-30 09:14:42 -05:00
}
2022-06-23 09:49:08 -04:00
defer unhook()
2020-01-30 09:14:42 -05:00
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 {
2022-12-15 06:26:08 -05:00
args.Filter = initFilters(opts.fileFilters)
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
2022-06-28 09:52:02 -04:00
defer setup(args.Owner)()
2022-06-23 09:49:08 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
if err != nil {
return "", err
2020-01-30 09:14:42 -05:00
}
2022-06-23 09:49:08 -04:00
defer unhook()
2020-01-30 09:14:42 -05:00
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
}
2022-06-22 10:14:52 -04:00
func pickFolders(opts options, multi bool) (string, []string, error) {
2022-06-28 09:52:02 -04:00
owner, _ := opts.attach.(win.HWND)
defer setup(owner)()
2020-01-02 20:44:23 -05:00
2022-06-22 10:14:52 -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
2022-06-21 19:58:14 -04:00
var dialog *win.IFileOpenDialog
2022-06-17 22:45:49 -04:00
err = win.CoCreateInstance(
2022-06-21 19:58:14 -04:00
win.CLSID_FileOpenDialog, nil, win.CLSCTX_ALL,
win.IID_IFileOpenDialog, unsafe.Pointer(&dialog))
2022-06-17 22:45:49 -04:00
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
}
2022-06-21 19:58:14 -04:00
defer dialog.Release()
2019-12-28 10:34:38 -05:00
2022-06-21 19:58:14 -04:00
flgs, err := dialog.GetOptions()
if err != nil {
return "", nil, err
2020-01-09 20:46:53 -05:00
}
2022-06-22 10:14:52 -04:00
flgs |= win.FOS_NOCHANGEDIR | win.FOS_PICKFOLDERS | win.FOS_FORCEFILESYSTEM
2020-01-09 20:46:53 -05:00
if multi {
2022-06-22 10:14:52 -04:00
flgs |= win.FOS_ALLOWMULTISELECT
2019-12-28 10:34:38 -05:00
}
2020-01-24 07:52:45 -05:00
if opts.showHidden {
2022-06-22 10:14:52 -04:00
flgs |= win.FOS_FORCESHOWHIDDEN
}
2022-06-21 19:58:14 -04:00
err = dialog.SetOptions(flgs)
if err != nil {
return "", nil, err
2019-12-28 10:34:38 -05:00
}
2021-03-03 22:25:45 -05:00
if opts.title != nil {
2022-06-21 19:58:14 -04:00
dialog.SetTitle(strptr(*opts.title))
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-21 19:58:14 -04:00
win.SHCreateItemFromParsingName(strptr(opts.filename), nil, win.IID_IShellItem, &item)
if item != nil {
2022-06-22 10:14:52 -04:00
defer item.Release()
2022-06-21 19:58:14 -04:00
dialog.SetFolder(item)
2019-12-28 10:34:38 -05:00
}
}
2022-07-26 09:49:49 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
if err != nil {
return "", nil, err
2020-01-30 20:31:54 -05:00
}
2022-07-26 09:49:49 -04:00
defer unhook()
2020-01-30 20:31:54 -05:00
2022-06-21 19:58:14 -04:00
err = dialog.Show(owner)
2020-01-30 20:31:54 -05:00
if opts.ctx != nil && opts.ctx.Err() != nil {
return "", nil, opts.ctx.Err()
}
2022-06-21 19:58:14 -04:00
if err == win.E_CANCELED {
2021-04-29 11:05:28 -04:00
return "", nil, ErrCanceled
2020-01-02 20:44:23 -05:00
}
2022-06-21 19:58:14 -04:00
if err != nil {
return "", nil, err
2019-12-28 10:34:38 -05:00
}
2020-01-09 20:46:53 -05:00
if multi {
2022-06-21 19:58:14 -04:00
items, err := dialog.GetResults()
if err != nil {
return "", nil, err
2020-01-09 20:46:53 -05:00
}
2022-06-21 19:58:14 -04:00
defer items.Release()
2020-01-09 20:46:53 -05:00
2022-06-21 19:58:14 -04:00
count, err := items.GetCount()
if err != nil {
return "", nil, err
2020-01-09 20:46:53 -05:00
}
2022-06-22 10:14:52 -04:00
var lst []string
for i := uint32(0); i < count && err == nil; i++ {
str, err := shellItemPath(items.GetItemAt(i))
2022-06-21 19:58:14 -04:00
if err != nil {
return "", nil, err
}
2022-06-22 10:14:52 -04:00
lst = append(lst, str)
2020-01-09 20:46:53 -05:00
}
2022-06-22 10:14:52 -04:00
return "", lst, nil
2020-01-09 20:46:53 -05:00
} else {
2022-06-22 10:14:52 -04:00
str, err := shellItemPath(dialog.GetResult())
2022-06-21 19:58:14 -04:00
if err != nil {
return "", nil, err
}
2022-06-22 10:14:52 -04:00
return str, nil, nil
}
}
func shellItemPath(item *win.IShellItem, err error) (string, error) {
if err != nil {
return "", err
2020-01-09 20:46:53 -05:00
}
2022-06-22 10:14:52 -04:00
defer item.Release()
return item.GetDisplayName(win.SIGDN_FILESYSPATH)
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
}
2022-07-26 09:49:49 -04:00
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
if err != nil {
return "", nil, err
2020-01-30 20:31:54 -05:00
}
2022-07-26 09:49:49 -04:00
defer unhook()
2020-01-30 20:31:54 -05:00
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-22 10:14:52 -04:00
if ptr == nil {
2021-04-29 11:05:28 -04:00
return "", nil, ErrCanceled
2019-12-13 20:07:25 -05:00
}
2022-06-22 10:14:52 -04:00
defer win.CoTaskMemFree(unsafe.Pointer(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-11-02 06:21:31 -04:00
dir = syscall.StringToUTF16Ptr(d)
2021-02-18 08:24:02 -05:00
}
2021-02-22 14:04:55 -05:00
if len(e) > 1 {
2022-11-02 06:21:31 -04:00
ext = syscall.StringToUTF16Ptr(e[1:])
2020-01-09 12:05:43 -05:00
}
return
2020-01-09 12:05:43 -05:00
}
2022-12-15 06:26:08 -05:00
func initFilters(filters FileFilters) *uint16 {
2021-04-13 08:28:26 -04:00
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 {
2022-12-15 06:26:08 -05:00
if len(f.Patterns) == 0 {
2022-12-14 22:05:44 -05:00
continue
}
2019-12-13 08:45:23 -05:00
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)
2022-12-15 06:26:08 -05:00
return &res[0]
2019-12-13 08:45:23 -05:00
}
2022-12-15 06:26:08 -05:00
return nil
2019-12-13 08:45:23 -05:00
}