2019-12-29 20:00:50 -05:00
|
|
|
package zenity
|
2019-12-13 08:45:23 -05:00
|
|
|
|
|
|
|
import (
|
2024-02-05 05:20:14 -05:00
|
|
|
"context"
|
2022-05-03 09:20:22 -04:00
|
|
|
"fmt"
|
2019-12-13 08:45:23 -05:00
|
|
|
"path/filepath"
|
2024-02-05 05:20:14 -05:00
|
|
|
"runtime"
|
2019-12-13 08:45:23 -05:00
|
|
|
"syscall"
|
|
|
|
"unicode/utf16"
|
|
|
|
"unsafe"
|
|
|
|
|
2024-07-10 00:06:01 -04:00
|
|
|
"git.bigun.dev/evan/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) {
|
2024-02-07 05:50:58 -05:00
|
|
|
name, _, shown, err := fileOpenDialog(opts, false)
|
|
|
|
if shown || opts.ctx != nil && opts.ctx.Err() != nil {
|
|
|
|
return name, err
|
|
|
|
}
|
2020-01-09 20:46:53 -05:00
|
|
|
if opts.directory {
|
2024-02-07 05:50:58 -05:00
|
|
|
return browseForFolder(opts)
|
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_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-11 22:18:22 -05:00
|
|
|
}
|
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))
|
2020-01-11 22:18:22 -05:00
|
|
|
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) {
|
2024-02-07 05:50:58 -05:00
|
|
|
_, list, shown, err := fileOpenDialog(opts, true)
|
|
|
|
if shown || opts.ctx != nil && opts.ctx.Err() != nil {
|
|
|
|
return list, err
|
|
|
|
}
|
2020-01-09 20:46:53 -05:00
|
|
|
if opts.directory {
|
2024-02-07 05:50:58 -05:00
|
|
|
return nil, fmt.Errorf("%w: multiple directory", ErrUnsupported)
|
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_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-11 22:18:22 -05:00
|
|
|
}
|
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))
|
2020-01-11 22:18:22 -05:00
|
|
|
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) {
|
2024-02-07 14:05:07 -05:00
|
|
|
if opts.directory {
|
|
|
|
return selectFile(opts)
|
|
|
|
}
|
2024-02-07 08:00:08 -05:00
|
|
|
name, shown, err := fileSaveDialog(opts)
|
|
|
|
if shown || opts.ctx != nil && opts.ctx.Err() != nil {
|
|
|
|
return name, 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-11 22:18:22 -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-11 22:18:22 -05:00
|
|
|
}
|
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))
|
2020-01-11 22:18:22 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-07 05:50:58 -05:00
|
|
|
func fileOpenDialog(opts options, multi bool) (string, []string, bool, error) {
|
2024-02-05 05:20:14 -05:00
|
|
|
uninit, err := coInitialize()
|
|
|
|
if err != nil {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, false, err
|
2024-02-05 05:20:14 -05:00
|
|
|
}
|
|
|
|
defer uninit()
|
|
|
|
|
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-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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, false, err
|
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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, false, err
|
2020-01-09 20:46:53 -05:00
|
|
|
}
|
2024-02-07 05:50:58 -05:00
|
|
|
flgs |= win.FOS_NOCHANGEDIR | win.FOS_FILEMUSTEXIST | 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
|
|
|
}
|
2024-02-07 05:50:58 -05:00
|
|
|
if opts.directory {
|
|
|
|
flgs |= win.FOS_PICKFOLDERS
|
|
|
|
}
|
2020-01-24 07:52:45 -05:00
|
|
|
if opts.showHidden {
|
2022-06-22 10:14:52 -04:00
|
|
|
flgs |= win.FOS_FORCESHOWHIDDEN
|
2020-01-11 22:18:22 -05:00
|
|
|
}
|
2022-06-21 19:58:14 -04:00
|
|
|
err = dialog.SetOptions(flgs)
|
|
|
|
if err != nil {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, false, 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
|
|
|
}
|
2024-02-07 13:58:08 -05:00
|
|
|
if opts.fileFilters != nil {
|
|
|
|
dialog.SetFileTypes(initFileTypes(opts.fileFilters))
|
|
|
|
}
|
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
|
2024-02-07 05:50:58 -05:00
|
|
|
dir, name, _ := splitDirAndName(opts.filename)
|
|
|
|
dialog.SetFileName(strptr(name))
|
|
|
|
if ext := filepath.Ext(name); len(ext) > 1 {
|
|
|
|
dialog.SetDefaultExtension(strptr(ext[1:]))
|
|
|
|
}
|
|
|
|
win.SHCreateItemFromParsingName(strptr(dir), nil, win.IID_IShellItem, &item)
|
2022-06-21 19:58:14 -04:00
|
|
|
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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, false, 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
|
|
|
|
2024-02-23 08:06:35 -05:00
|
|
|
if opts.ctx != nil && opts.ctx.Done() != nil {
|
|
|
|
wait := make(chan struct{})
|
|
|
|
defer close(wait)
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-opts.ctx.Done():
|
|
|
|
dialog.Close(win.E_TIMEOUT)
|
|
|
|
case <-wait:
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, opts.ctx.Err()
|
2020-01-30 20:31:54 -05:00
|
|
|
}
|
2022-06-21 19:58:14 -04:00
|
|
|
if err == win.E_CANCELED {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, ErrCanceled
|
2020-01-02 20:44:23 -05:00
|
|
|
}
|
2022-06-21 19:58:14 -04:00
|
|
|
if err != nil {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, 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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, 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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, 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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, err
|
2022-06-21 19:58:14 -04:00
|
|
|
}
|
2022-06-22 10:14:52 -04:00
|
|
|
lst = append(lst, str)
|
2020-01-09 20:46:53 -05:00
|
|
|
}
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", lst, true, 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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", nil, true, err
|
2022-06-21 19:58:14 -04:00
|
|
|
}
|
2024-02-07 05:50:58 -05:00
|
|
|
return str, nil, true, nil
|
2022-06-22 10:14:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 08:00:08 -05:00
|
|
|
func fileSaveDialog(opts options) (string, bool, error) {
|
|
|
|
uninit, err := coInitialize()
|
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
defer uninit()
|
|
|
|
|
|
|
|
owner, _ := opts.attach.(win.HWND)
|
|
|
|
defer setup(owner)()
|
|
|
|
|
|
|
|
var dialog *win.IFileSaveDialog
|
|
|
|
err = win.CoCreateInstance(
|
|
|
|
win.CLSID_FileSaveDialog, nil, win.CLSCTX_ALL,
|
|
|
|
win.IID_IFileSaveDialog, unsafe.Pointer(&dialog))
|
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
defer dialog.Release()
|
|
|
|
|
|
|
|
flgs, err := dialog.GetOptions()
|
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
flgs |= win.FOS_NOCHANGEDIR | win.FOS_PATHMUSTEXIST | win.FOS_NOREADONLYRETURN | win.FOS_FORCEFILESYSTEM
|
|
|
|
if opts.confirmOverwrite {
|
|
|
|
flgs |= win.FOS_OVERWRITEPROMPT
|
|
|
|
}
|
|
|
|
if opts.confirmCreate {
|
|
|
|
flgs |= win.FOS_CREATEPROMPT
|
|
|
|
}
|
|
|
|
if opts.showHidden {
|
|
|
|
flgs |= win.FOS_FORCESHOWHIDDEN
|
|
|
|
}
|
|
|
|
err = dialog.SetOptions(flgs)
|
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.title != nil {
|
|
|
|
dialog.SetTitle(strptr(*opts.title))
|
|
|
|
}
|
2024-02-07 13:58:08 -05:00
|
|
|
if opts.fileFilters != nil {
|
|
|
|
dialog.SetFileTypes(initFileTypes(opts.fileFilters))
|
|
|
|
}
|
2024-02-07 08:00:08 -05:00
|
|
|
|
|
|
|
if opts.filename != "" {
|
|
|
|
var item *win.IShellItem
|
|
|
|
dir, name, _ := splitDirAndName(opts.filename)
|
|
|
|
dialog.SetFileName(strptr(name))
|
|
|
|
if ext := filepath.Ext(name); len(ext) > 1 {
|
|
|
|
dialog.SetDefaultExtension(strptr(ext[1:]))
|
|
|
|
}
|
|
|
|
win.SHCreateItemFromParsingName(strptr(dir), nil, win.IID_IShellItem, &item)
|
|
|
|
if item != nil {
|
|
|
|
defer item.Release()
|
|
|
|
dialog.SetFolder(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
|
|
|
}
|
|
|
|
defer unhook()
|
|
|
|
|
2024-02-23 08:06:35 -05:00
|
|
|
if opts.ctx != nil && opts.ctx.Done() != nil {
|
|
|
|
wait := make(chan struct{})
|
|
|
|
defer close(wait)
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-opts.ctx.Done():
|
|
|
|
dialog.Close(win.E_TIMEOUT)
|
|
|
|
case <-wait:
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2024-02-07 08:00:08 -05:00
|
|
|
err = dialog.Show(owner)
|
|
|
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
|
|
|
return "", true, opts.ctx.Err()
|
|
|
|
}
|
|
|
|
if err == win.E_CANCELED {
|
|
|
|
return "", true, ErrCanceled
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return "", true, err
|
|
|
|
}
|
|
|
|
|
|
|
|
str, err := shellItemPath(dialog.GetResult())
|
|
|
|
if err != nil {
|
|
|
|
return "", true, err
|
|
|
|
}
|
|
|
|
return str, true, nil
|
|
|
|
}
|
|
|
|
|
2022-06-22 10:14:52 -04:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-02-07 05:50:58 -05:00
|
|
|
func browseForFolder(opts options) (string, error) {
|
|
|
|
uninit, err := coInitialize()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer uninit()
|
|
|
|
|
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)
|
2020-01-12 13:55:10 -05:00
|
|
|
}
|
|
|
|
if opts.filename != "" {
|
2022-06-20 11:05:11 -04:00
|
|
|
args.LParam = strptr(opts.filename)
|
2021-09-13 08:17:15 -04:00
|
|
|
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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", 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 {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", opts.ctx.Err()
|
2020-01-30 20:31:54 -05:00
|
|
|
}
|
2022-06-22 10:14:52 -04:00
|
|
|
if ptr == nil {
|
2024-02-07 05:50:58 -05:00
|
|
|
return "", 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[:])
|
2024-02-07 05:50:58 -05:00
|
|
|
return 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)
|
2021-09-13 08:17:15 -04:00
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2024-02-05 05:20:14 -05:00
|
|
|
func coInitialize() (context.CancelFunc, error) {
|
|
|
|
runtime.LockOSThread()
|
2024-02-23 08:06:35 -05:00
|
|
|
// .NET uses MTA for all background threads, so do the same.
|
|
|
|
// If someone needs STA because they're doing UI,
|
|
|
|
// they should initialize COM themselves before.
|
|
|
|
err := win.CoInitializeEx(0, win.COINIT_MULTITHREADED|win.COINIT_DISABLE_OLE1DDE)
|
|
|
|
if err == win.S_FALSE {
|
|
|
|
// COM was already initialized, we simply increased the ref count.
|
|
|
|
// Make this a no-op by decreasing our ref count.
|
|
|
|
win.CoUninitialize()
|
|
|
|
return runtime.UnlockOSThread, nil
|
|
|
|
}
|
|
|
|
// Don't uninitialize COM; this is against the docs, but it's what .NET does.
|
|
|
|
// Eventually all threads will have COM initialized.
|
|
|
|
if err == nil || err == win.RPC_E_CHANGED_MODE {
|
2024-02-05 05:20:14 -05:00
|
|
|
return runtime.UnlockOSThread, nil
|
|
|
|
}
|
|
|
|
runtime.UnlockOSThread()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-11 22:18:22 -05:00
|
|
|
func initDirNameExt(filename string, name []uint16) (dir *uint16, ext *uint16) {
|
2023-04-12 06:22:30 -04:00
|
|
|
d, n, _ := splitDirAndName(filename)
|
2021-02-18 08:24:02 -05:00
|
|
|
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
|
|
|
}
|
2020-01-11 22:18:22 -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
|
|
|
|
}
|
2024-02-07 13:58:08 -05:00
|
|
|
res = append(res, syscall.StringToUTF16(f.Name)...)
|
2020-01-08 19:54:34 -05:00
|
|
|
for _, p := range f.Patterns {
|
2024-02-07 13:58:08 -05:00
|
|
|
res = append(res, syscall.StringToUTF16(p)...)
|
|
|
|
res[len(res)-1] = ';'
|
2019-12-13 08:45:23 -05:00
|
|
|
}
|
|
|
|
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
|
|
|
}
|
2024-02-07 13:58:08 -05:00
|
|
|
|
|
|
|
func initFileTypes(filters FileFilters) (int, *win.COMDLG_FILTERSPEC) {
|
|
|
|
filters.simplify()
|
|
|
|
filters.name()
|
|
|
|
var res []win.COMDLG_FILTERSPEC
|
|
|
|
for _, f := range filters {
|
|
|
|
if len(f.Patterns) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var spec []uint16
|
|
|
|
for _, p := range f.Patterns {
|
|
|
|
spec = append(spec, syscall.StringToUTF16(p)...)
|
|
|
|
spec[len(spec)-1] = ';'
|
|
|
|
}
|
|
|
|
res = append(res, win.COMDLG_FILTERSPEC{
|
|
|
|
Name: syscall.StringToUTF16Ptr(f.Name),
|
|
|
|
Spec: &spec[0],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return len(res), &res[0]
|
|
|
|
}
|