diff --git a/file_test.go b/file_test.go index 8cafe97..0fe0d8e 100644 --- a/file_test.go +++ b/file_test.go @@ -3,7 +3,6 @@ package zenity_test import ( "context" "errors" - "fmt" "os" "path/filepath" "testing" @@ -124,7 +123,7 @@ func TestSelectFile_script(t *testing.T) { } t.Run("Cancel", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file selection dialog, cancel.")) + zenity.Info("In the file selection dialog, cancel.") str, err := zenity.SelectFile() if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -134,7 +133,7 @@ func TestSelectFile_script(t *testing.T) { } }) t.Run("File", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file selection dialog, pick any file.")) + zenity.Info("In the file selection dialog, pick any file.") str, err := zenity.SelectFile() if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -147,7 +146,7 @@ func TestSelectFile_script(t *testing.T) { } }) t.Run("Directory", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file selection dialog, pick any directory.")) + zenity.Info("In the file selection dialog, pick any directory.") str, err := zenity.SelectFile(zenity.Directory()) if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -169,7 +168,7 @@ func TestSelectFileMultiple_script(t *testing.T) { } t.Run("Cancel", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file selection dialog, cancel.")) + zenity.Info("In the file selection dialog, cancel.") lst, err := zenity.SelectFileMultiple() if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -179,7 +178,7 @@ func TestSelectFileMultiple_script(t *testing.T) { } }) t.Run("Files", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file selection dialog, pick two files.")) + zenity.Info("In the file selection dialog, pick two files.") lst, err := zenity.SelectFileMultiple() if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -194,7 +193,7 @@ func TestSelectFileMultiple_script(t *testing.T) { } }) t.Run("Directories", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file selection dialog, pick two directories.")) + zenity.Info("In the file selection dialog, pick two directories.") lst, err := zenity.SelectFileMultiple(zenity.Directory()) if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -221,7 +220,7 @@ func TestSelectFileSave_script(t *testing.T) { } t.Run("Cancel", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file save dialog, cancel.")) + zenity.Info("In the file save dialog, cancel.") str, err := zenity.SelectFileSave() if skip, err := skip(err); skip { t.Skip("skipping:", err) @@ -231,7 +230,7 @@ func TestSelectFileSave_script(t *testing.T) { } }) t.Run("Name", func(t *testing.T) { - zenity.Info(fmt.Sprintf("In the file save dialog, press OK.")) + zenity.Info("In the file save dialog, press OK.") str, err := zenity.SelectFileSave( zenity.ConfirmOverwrite(), zenity.Filename("Χρτο.go"), diff --git a/file_windows.go b/file_windows.go index 478b42a..5b3a9c5 100644 --- a/file_windows.go +++ b/file_windows.go @@ -222,6 +222,9 @@ func fileOpenDialog(opts options, multi bool) (string, []string, bool, error) { if opts.title != nil { dialog.SetTitle(strptr(*opts.title)) } + if opts.fileFilters != nil { + dialog.SetFileTypes(initFileTypes(opts.fileFilters)) + } if opts.filename != "" { var item *win.IShellItem @@ -325,6 +328,9 @@ func fileSaveDialog(opts options) (string, bool, error) { if opts.title != nil { dialog.SetTitle(strptr(*opts.title)) } + if opts.fileFilters != nil { + dialog.SetFileTypes(initFileTypes(opts.fileFilters)) + } if opts.filename != "" { var item *win.IShellItem @@ -459,11 +465,10 @@ func initFilters(filters FileFilters) *uint16 { if len(f.Patterns) == 0 { continue } - res = append(res, utf16.Encode([]rune(f.Name))...) - res = append(res, 0) + res = append(res, syscall.StringToUTF16(f.Name)...) for _, p := range f.Patterns { - res = append(res, utf16.Encode([]rune(p))...) - res = append(res, uint16(';')) + res = append(res, syscall.StringToUTF16(p)...) + res[len(res)-1] = ';' } res = append(res, 0) } @@ -473,3 +478,24 @@ func initFilters(filters FileFilters) *uint16 { } return nil } + +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] +} diff --git a/internal/win/shell32.go b/internal/win/shell32.go index af187d7..5b107e4 100644 --- a/internal/win/shell32.go +++ b/internal/win/shell32.go @@ -46,43 +46,51 @@ const ( // NOTIFYICONDATA state NIS_HIDDEN = 0x1 NIS_SHAREDICON = 0x2 +) - // IFileOpenDialog options - FOS_OVERWRITEPROMPT = 0x2 - FOS_STRICTFILETYPES = 0x4 - FOS_NOCHANGEDIR = 0x8 - FOS_PICKFOLDERS = 0x20 - FOS_FORCEFILESYSTEM = 0x40 - FOS_ALLNONSTORAGEITEMS = 0x80 - FOS_NOVALIDATE = 0x100 - FOS_ALLOWMULTISELECT = 0x200 - FOS_PATHMUSTEXIST = 0x800 - FOS_FILEMUSTEXIST = 0x1000 - FOS_CREATEPROMPT = 0x2000 - FOS_SHAREAWARE = 0x4000 - FOS_NOREADONLYRETURN = 0x8000 - FOS_NOTESTFILECREATE = 0x10000 - FOS_HIDEMRUPLACES = 0x20000 - FOS_HIDEPINNEDPLACES = 0x40000 - FOS_NODEREFERENCELINKS = 0x100000 - FOS_OKBUTTONNEEDSINTERACTION = 0x200000 - FOS_DONTADDTORECENT = 0x2000000 - FOS_FORCESHOWHIDDEN = 0x10000000 - FOS_DEFAULTNOMINIMODE = 0x20000000 - FOS_FORCEPREVIEWPANEON = 0x40000000 - FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 +// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions +type _FILEOPENDIALOGOPTIONS int - // IShellItem.GetDisplayName forms - SIGDN_NORMALDISPLAY = 0x00000000 - SIGDN_PARENTRELATIVEPARSING = ^(^0x18001 + 0x80000000) - SIGDN_DESKTOPABSOLUTEPARSING = ^(^0x28000 + 0x80000000) - SIGDN_PARENTRELATIVEEDITING = ^(^0x31001 + 0x80000000) - SIGDN_DESKTOPABSOLUTEEDITING = ^(^0x4c000 + 0x80000000) - SIGDN_FILESYSPATH = ^(^0x58000 + 0x80000000) - SIGDN_URL = ^(^0x68000 + 0x80000000) - SIGDN_PARENTRELATIVEFORADDRESSBAR = ^(^0x7c001 + 0x80000000) - SIGDN_PARENTRELATIVE = ^(^0x80001 + 0x80000000) - SIGDN_PARENTRELATIVEFORUI = ^(^0x94001 + 0x80000000) +const ( + FOS_OVERWRITEPROMPT _FILEOPENDIALOGOPTIONS = 0x2 + FOS_STRICTFILETYPES _FILEOPENDIALOGOPTIONS = 0x4 + FOS_NOCHANGEDIR _FILEOPENDIALOGOPTIONS = 0x8 + FOS_PICKFOLDERS _FILEOPENDIALOGOPTIONS = 0x20 + FOS_FORCEFILESYSTEM _FILEOPENDIALOGOPTIONS = 0x40 + FOS_ALLNONSTORAGEITEMS _FILEOPENDIALOGOPTIONS = 0x80 + FOS_NOVALIDATE _FILEOPENDIALOGOPTIONS = 0x100 + FOS_ALLOWMULTISELECT _FILEOPENDIALOGOPTIONS = 0x200 + FOS_PATHMUSTEXIST _FILEOPENDIALOGOPTIONS = 0x800 + FOS_FILEMUSTEXIST _FILEOPENDIALOGOPTIONS = 0x1000 + FOS_CREATEPROMPT _FILEOPENDIALOGOPTIONS = 0x2000 + FOS_SHAREAWARE _FILEOPENDIALOGOPTIONS = 0x4000 + FOS_NOREADONLYRETURN _FILEOPENDIALOGOPTIONS = 0x8000 + FOS_NOTESTFILECREATE _FILEOPENDIALOGOPTIONS = 0x10000 + FOS_HIDEMRUPLACES _FILEOPENDIALOGOPTIONS = 0x20000 + FOS_HIDEPINNEDPLACES _FILEOPENDIALOGOPTIONS = 0x40000 + FOS_NODEREFERENCELINKS _FILEOPENDIALOGOPTIONS = 0x100000 + FOS_OKBUTTONNEEDSINTERACTION _FILEOPENDIALOGOPTIONS = 0x200000 + FOS_DONTADDTORECENT _FILEOPENDIALOGOPTIONS = 0x2000000 + FOS_FORCESHOWHIDDEN _FILEOPENDIALOGOPTIONS = 0x10000000 + FOS_DEFAULTNOMINIMODE _FILEOPENDIALOGOPTIONS = 0x20000000 + FOS_FORCEPREVIEWPANEON _FILEOPENDIALOGOPTIONS = 0x40000000 + FOS_SUPPORTSTREAMABLEITEMS _FILEOPENDIALOGOPTIONS = 0x80000000 +) + +// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-sigdn +type SIGDN int + +const ( + SIGDN_NORMALDISPLAY SIGDN = 0x00000000 + SIGDN_PARENTRELATIVEPARSING SIGDN = ^(^0x18001 + 0x80000000) + SIGDN_DESKTOPABSOLUTEPARSING SIGDN = ^(^0x28000 + 0x80000000) + SIGDN_PARENTRELATIVEEDITING SIGDN = ^(^0x31001 + 0x80000000) + SIGDN_DESKTOPABSOLUTEEDITING SIGDN = ^(^0x4c000 + 0x80000000) + SIGDN_FILESYSPATH SIGDN = ^(^0x58000 + 0x80000000) + SIGDN_URL SIGDN = ^(^0x68000 + 0x80000000) + SIGDN_PARENTRELATIVEFORADDRESSBAR SIGDN = ^(^0x7c001 + 0x80000000) + SIGDN_PARENTRELATIVE SIGDN = ^(^0x80001 + 0x80000000) + SIGDN_PARENTRELATIVEFORUI SIGDN = ^(^0x94001 + 0x80000000) ) // https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfow @@ -116,7 +124,14 @@ type NOTIFYICONDATA struct { // BalloonIcon Handle // NOTIFYICONDATAA_V3_SIZE } -type IDLIST struct{} +// https://docs.microsoft.com/en-us/windows/win32/api/shtypes/ns-shtypes-comdlg_filterspec +type COMDLG_FILTERSPEC struct { + Name *uint16 + Spec *uint16 +} + +// https://docs.microsoft.com/en-us/windows/win32/api/shtypes/ns-shtypes-itemidlist +type ITEMIDLIST struct{} // https://github.com/wine-mirror/wine/blob/master/include/shobjidl.idl @@ -182,7 +197,16 @@ type iFileDialogVtbl struct { SetFilter uintptr } -func (u *IFileDialog) SetOptions(fos int) (err error) { +func (u *IFileDialog) SetFileTypes(cFileTypes int, rgFilterSpec *COMDLG_FILTERSPEC) (err error) { + vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) + hr, _, _ := u.call(vtbl.SetFileTypes, uintptr(cFileTypes), uintptr(unsafe.Pointer(rgFilterSpec))) + if hr != 0 { + err = syscall.Errno(hr) + } + return +} + +func (u *IFileDialog) SetOptions(fos _FILEOPENDIALOGOPTIONS) (err error) { vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) hr, _, _ := u.call(vtbl.SetOptions, uintptr(fos)) if hr != 0 { @@ -191,7 +215,7 @@ func (u *IFileDialog) SetOptions(fos int) (err error) { return } -func (u *IFileDialog) GetOptions() (fos int, err error) { +func (u *IFileDialog) GetOptions() (fos _FILEOPENDIALOGOPTIONS, err error) { vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) hr, _, _ := u.call(vtbl.GetOptions, uintptr(unsafe.Pointer(&fos))) if hr != 0 { @@ -270,7 +294,7 @@ type iShellItemVtbl struct { Compare uintptr } -func (u *IShellItem) GetDisplayName(name int) (res string, err error) { +func (u *IShellItem) GetDisplayName(name SIGDN) (res string, err error) { var ptr *uint16 vtbl := *(**iShellItemVtbl)(unsafe.Pointer(u)) hr, _, _ := u.call(vtbl.GetDisplayName, uintptr(name), uintptr(unsafe.Pointer(&ptr))) @@ -314,7 +338,7 @@ func (u *IShellItemArray) GetItemAt(index uint32) (item *IShellItem, err error) } //sys ExtractAssociatedIcon(instance Handle, path *uint16, icon *uint16) (ret Handle, err error) = shell32.ExtractAssociatedIconW -//sys SHBrowseForFolder(bi *BROWSEINFO) (ret *IDLIST) = shell32.SHBrowseForFolder +//sys SHBrowseForFolder(bi *BROWSEINFO) (ret *ITEMIDLIST) = shell32.SHBrowseForFolder //sys SHCreateItemFromParsingName(path *uint16, bc *IBindCtx, iid *GUID, item **IShellItem) (res error) = shell32.SHCreateItemFromParsingName //sys ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ok bool) = shell32.Shell_NotifyIconW -//sys SHGetPathFromIDListEx(ptr *IDLIST, path *uint16, pathLen int, opts int) (ok bool) = shell32.SHGetPathFromIDListEx +//sys SHGetPathFromIDListEx(ptr *ITEMIDLIST, path *uint16, pathLen int, opts int) (ok bool) = shell32.SHGetPathFromIDListEx diff --git a/internal/win/zsyscall_windows.go b/internal/win/zsyscall_windows.go index d5f6d8e..0db7013 100644 --- a/internal/win/zsyscall_windows.go +++ b/internal/win/zsyscall_windows.go @@ -235,9 +235,9 @@ func ExtractAssociatedIcon(instance Handle, path *uint16, icon *uint16) (ret Han return } -func SHBrowseForFolder(bi *BROWSEINFO) (ret *IDLIST) { +func SHBrowseForFolder(bi *BROWSEINFO) (ret *ITEMIDLIST) { r0, _, _ := syscall.Syscall(procSHBrowseForFolder.Addr(), 1, uintptr(unsafe.Pointer(bi)), 0, 0) - ret = (*IDLIST)(unsafe.Pointer(r0)) + ret = (*ITEMIDLIST)(unsafe.Pointer(r0)) return } @@ -249,7 +249,7 @@ func SHCreateItemFromParsingName(path *uint16, bc *IBindCtx, iid *GUID, item **I return } -func SHGetPathFromIDListEx(ptr *IDLIST, path *uint16, pathLen int, opts int) (ok bool) { +func SHGetPathFromIDListEx(ptr *ITEMIDLIST, path *uint16, pathLen int, opts int) (ok bool) { r0, _, _ := syscall.Syscall6(procSHGetPathFromIDListEx.Addr(), 4, uintptr(unsafe.Pointer(ptr)), uintptr(unsafe.Pointer(path)), uintptr(pathLen), uintptr(opts), 0, 0) ok = r0 != 0 return