@@ -8,6 +8,7 @@ import 'package:file_picker/src/utils.dart';
88import 'package:file_picker/src/exceptions.dart' ;
99import 'package:file_picker/src/windows/file_picker_windows_ffi_types.dart' ;
1010import 'package:path/path.dart' ;
11+ import 'package:win32/win32.dart' ;
1112
1213FilePicker filePickerWithFFI () => FilePickerWindows ();
1314
@@ -59,20 +60,82 @@ class FilePickerWindows extends FilePicker {
5960 return returnValue;
6061 }
6162
63+ /// See API spec:
64+ /// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifiledialog
65+ /// See example implementation:
66+ /// https://github.com/timsneath/win32/blob/main/example/dialogshow.dart
6267 @override
6368 Future <String ?> getDirectoryPath ({
6469 String ? dialogTitle,
6570 bool lockParentWindow = false ,
6671 String ? initialDirectory,
6772 }) {
68- final pathIdPointer =
69- _pickDirectory (dialogTitle ?? defaultDialogTitle, lockParentWindow);
70- if (pathIdPointer == null ) {
71- return Future .value (null );
73+ int hr = CoInitializeEx (
74+ nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE );
75+
76+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
77+
78+ final fileDialog = FileOpenDialog .createInstance ();
79+
80+ final optionsPointer = calloc <Uint32 >();
81+ hr = fileDialog.GetOptions (optionsPointer);
82+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
83+
84+ final options = optionsPointer.value |
85+ FILEOPENDIALOGOPTIONS .FOS_PICKFOLDERS |
86+ FILEOPENDIALOGOPTIONS .FOS_FORCEFILESYSTEM ;
87+ hr = fileDialog.SetOptions (options);
88+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
89+
90+ final title = TEXT (dialogTitle ?? defaultDialogTitle);
91+ hr = fileDialog.SetTitle (title);
92+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
93+ free (title);
94+
95+ // TODO: figure out how to set the initial directory via SetDefaultFolder / SetFolder
96+ // if (initialDirectory != null) {
97+ // final folder = TEXT(initialDirectory);
98+ // final riid = calloc<COMObject>();
99+ // final item = IShellItem(riid);
100+ // final location = item.ptr;
101+ // SHCreateItemFromParsingName(folder, nullptr, riid.cast(), item.ptr.cast());
102+ // hr = fileDialog.AddPlace(item.ptr, FDAP.FDAP_TOP);
103+ // if (!SUCCEEDED(hr)) throw WindowsException(hr);
104+ // hr = fileDialog.SetFolder(location);
105+ // if (!SUCCEEDED(hr)) throw WindowsException(hr);
106+ // free(folder);
107+ // }
108+
109+ final hwndOwner = lockParentWindow ? GetForegroundWindow () : NULL ;
110+ hr = fileDialog.Show (hwndOwner);
111+ if (! SUCCEEDED (hr)) {
112+ fileDialog.Release ();
113+ CoUninitialize ();
114+
115+ if (hr == HRESULT_FROM_WIN32 (ERROR_CANCELLED )) {
116+ return Future .value (null );
117+ }
118+ throw WindowsException (hr);
72119 }
73- return Future .value (
74- _getPathFromItemIdentifierList (pathIdPointer),
75- );
120+
121+ final ppsi = calloc <COMObject >();
122+ hr = fileDialog.GetResult (ppsi.cast ());
123+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
124+
125+ final item = IShellItem (ppsi);
126+ final pathPtr = calloc <Pointer <Utf16 >>();
127+ hr = item.GetDisplayName (SIGDN .SIGDN_FILESYSPATH , pathPtr);
128+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
129+
130+ final path = pathPtr.value.toDartString ();
131+
132+ hr = item.Release ();
133+ if (! SUCCEEDED (hr)) throw WindowsException (hr);
134+
135+ hr = fileDialog.Release ();
136+ CoUninitialize ();
137+
138+ return Future .value (path);
76139 }
77140
78141 @override
@@ -138,66 +201,6 @@ class FilePickerWindows extends FilePicker {
138201 }
139202 }
140203
141- /// Uses the Win32 API to display a dialog box that enables the user to select a folder.
142- ///
143- /// Returns a PIDL that specifies the location of the selected folder relative to the root of the
144- /// namespace. Returns null, if the user clicked on the "Cancel" button in the dialog box.
145- Pointer ? _pickDirectory (String dialogTitle, bool lockParentWindow) {
146- final shell32 = DynamicLibrary .open ('shell32.dll' );
147-
148- final shBrowseForFolderW =
149- shell32.lookupFunction <SHBrowseForFolderW , SHBrowseForFolderW >(
150- 'SHBrowseForFolderW' );
151-
152- final Pointer <BROWSEINFOA > browseInfo = calloc <BROWSEINFOA >();
153- if (lockParentWindow) {
154- browseInfo.ref.hwndOwner = _getWindowHandle ();
155- }
156- browseInfo.ref.pidlRoot = nullptr;
157- browseInfo.ref.pszDisplayName = calloc.allocate <Utf16 >(maximumPathLength);
158- browseInfo.ref.lpszTitle = dialogTitle.toNativeUtf16 ();
159- browseInfo.ref.ulFlags =
160- bifEditBox | bifNewDialogStyle | bifReturnOnlyFsDirs;
161-
162- final Pointer <NativeType > itemIdentifierList =
163- shBrowseForFolderW (browseInfo);
164-
165- calloc.free (browseInfo.ref.pszDisplayName);
166- calloc.free (browseInfo.ref.lpszTitle);
167- calloc.free (browseInfo);
168-
169- if (itemIdentifierList == nullptr) {
170- return null ;
171- }
172- return itemIdentifierList;
173- }
174-
175- /// Uses the Win32 API to convert an item identifier list to a file system path.
176- ///
177- /// [lpItem] must contain the address of an item identifier list that specifies a
178- /// file or directory location relative to the root of the namespace (the desktop).
179- /// Returns the file system path as a [String] . Throws an exception, if the
180- /// conversion wasn't successful.
181- String _getPathFromItemIdentifierList (Pointer lpItem) {
182- final shell32 = DynamicLibrary .open ('shell32.dll' );
183-
184- final shGetPathFromIDListW =
185- shell32.lookupFunction <SHGetPathFromIDListW , SHGetPathFromIDListWDart >(
186- 'SHGetPathFromIDListW' );
187-
188- final Pointer <Utf16 > pszPath = calloc.allocate <Utf16 >(maximumPathLength);
189-
190- final int result = shGetPathFromIDListW (lpItem, pszPath);
191- if (result == 0x00000000 ) {
192- throw Exception (
193- 'Failed to convert item identifier list to a file system path.' );
194- }
195-
196- final path = pszPath.toDartString ();
197- calloc.free (pszPath);
198- return path;
199- }
200-
201204 /// Extracts the list of selected files from the Win32 API struct [OPENFILENAMEW] .
202205 ///
203206 /// After the user has closed the file picker dialog, Win32 API sets the property
0 commit comments