1- using System . Runtime . Versioning ;
1+ using System . Diagnostics ;
2+ using System . Runtime . Versioning ;
3+ using TerraFX . Interop . Windows ;
24
35namespace AssetRipper . NativeDialogs ;
46
@@ -30,9 +32,88 @@ public static class OpenFolderDialog
3032 }
3133
3234 [ SupportedOSPlatform ( "windows" ) ]
33- private unsafe static Task < string ? > OpenFolderWindows ( )
35+ private static Task < string ? > OpenFolderWindows ( )
3436 {
35- return Task . FromResult < string ? > ( null ) ;
37+ TaskCompletionSource < string ? > tcs = new ( ) ;
38+
39+ Thread thread = new ( ( ) =>
40+ {
41+ try
42+ {
43+ // Run the STA work
44+ Debug . Assert ( OperatingSystem . IsWindows ( ) ) ;
45+ string ? result = OpenFolderWindowsInternal ( ) ;
46+
47+ // Mark task complete
48+ tcs . SetResult ( result ) ;
49+ }
50+ catch ( Exception ex )
51+ {
52+ tcs . SetException ( ex ) ;
53+ }
54+ } ) ;
55+ thread . SetApartmentState ( ApartmentState . STA ) ;
56+ thread . Start ( ) ;
57+
58+ return tcs . Task ;
59+ }
60+
61+ [ SupportedOSPlatform ( "windows" ) ]
62+ private unsafe static string ? OpenFolderWindowsInternal ( )
63+ {
64+ string ? result = null ;
65+
66+ HRESULT hr = Windows . CoInitializeEx ( null , ( uint ) COINIT . COINIT_APARTMENTTHREADED ) ;
67+ switch ( hr . Value )
68+ {
69+ case S . S_OK :
70+ Windows . CoUninitialize ( ) ;
71+ throw new InvalidOperationException ( "CoInitializeEx failed with S_OK, which should never happen because .NET is supposed to initialize the thread." ) ;
72+ case S . S_FALSE :
73+ // The thread is already initialized, which is expected.
74+ break ;
75+ case RPC . RPC_E_CHANGED_MODE :
76+ // The thread is already initialized with a different mode, which is unexpected.
77+ throw new InvalidOperationException ( "CoInitializeEx failed with RPC_E_CHANGED_MODE, which should never happen because we only call this method in STA threads." ) ;
78+ }
79+
80+ IFileOpenDialog * pFileDialog = null ;
81+
82+ // Assign the CLSID and IID for the FileOpenDialog.
83+ Guid CLSID_FileOpenDialog = new ( "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7" ) ;
84+ Guid IID_IFileOpenDialog = new ( "D57C7288-D4AD-4768-BE02-9D969532D960" ) ;
85+
86+ // Create the FileOpenDialog object.
87+ hr = Windows . CoCreateInstance ( & CLSID_FileOpenDialog , null , ( uint ) CLSCTX . CLSCTX_INPROC_SERVER , & IID_IFileOpenDialog , ( void * * ) & pFileDialog ) ;
88+ if ( Windows . SUCCEEDED ( hr ) )
89+ {
90+ // Set the options on the dialog.
91+ uint dwOptions ;
92+ pFileDialog ->GetOptions ( & dwOptions ) ;
93+ pFileDialog ->SetOptions ( dwOptions | ( uint ) FILEOPENDIALOGOPTIONS . FOS_PICKFOLDERS | ( uint ) FILEOPENDIALOGOPTIONS . FOS_FORCEFILESYSTEM ) ;
94+
95+ // Show the dialog
96+ hr = pFileDialog ->Show ( default ) ;
97+ if ( Windows . SUCCEEDED ( hr ) )
98+ {
99+ IShellItem * pItem ;
100+ hr = pFileDialog ->GetResult ( & pItem ) ;
101+ if ( Windows . SUCCEEDED ( hr ) )
102+ {
103+ char * pszFilePath = null ;
104+ hr = pItem ->GetDisplayName ( SIGDN . SIGDN_FILESYSPATH , & pszFilePath ) ;
105+ if ( Windows . SUCCEEDED ( hr ) )
106+ {
107+ result = new string ( pszFilePath ) ;
108+ Windows . CoTaskMemFree ( pszFilePath ) ;
109+ }
110+ pItem ->Release ( ) ;
111+ }
112+ }
113+ pFileDialog ->Release ( ) ;
114+ }
115+
116+ return result ;
36117 }
37118
38119 [ SupportedOSPlatform ( "macos" ) ]
@@ -44,7 +125,37 @@ public static class OpenFolderDialog
44125 [ SupportedOSPlatform ( "linux" ) ]
45126 private static Task < string ? > OpenFolderLinux ( )
46127 {
47- return Task . FromResult < string ? > ( null ) ;
128+ if ( Gtk . Global . IsSupported )
129+ {
130+ string ? result ;
131+ Gtk . Application . Init ( ) ; // spins a main loop
132+ try
133+ {
134+ using Gtk . FileChooserNative dlg = new (
135+ "Open a folder" , null ,
136+ Gtk . FileChooserAction . SelectFolder , "Open" , "Cancel" ) ;
137+
138+ if ( dlg . Run ( ) == ( int ) Gtk . ResponseType . Accept )
139+ {
140+ result = dlg . Filename ;
141+ }
142+ else
143+ {
144+ result = null ; // User canceled the dialog
145+ }
146+ }
147+ finally
148+ {
149+ Gtk . Application . Quit ( ) ; // stops the main loop
150+ }
151+
152+ return Task . FromResult ( result ) ;
153+ }
154+ else
155+ {
156+ // Fallback
157+ return Task . FromResult < string ? > ( null ) ;
158+ }
48159 }
49160
50161 public static Task < string [ ] ? > OpenFolders ( )
@@ -101,14 +212,40 @@ public static class OpenFolderDialog
101212 }
102213
103214 [ SupportedOSPlatform ( "linux" ) ]
104- private static async Task < string [ ] ? > OpenFoldersLinux ( )
215+ private static Task < string [ ] ? > OpenFoldersLinux ( )
105216 {
106- // Todo: proper Linux implementation
107- string ? path = await OpenFolder ( ) ;
108- if ( string . IsNullOrEmpty ( path ) )
217+ if ( Gtk . Global . IsSupported )
109218 {
110- return null ; // User canceled the dialog
219+ string [ ] ? result ;
220+ Gtk . Application . Init ( ) ; // spins a main loop
221+ try
222+ {
223+ using Gtk . FileChooserNative dlg = new (
224+ "Open folders" , null ,
225+ Gtk . FileChooserAction . SelectFolder , "Open" , "Cancel" ) ;
226+
227+ dlg . SelectMultiple = true ; // Allow multiple folder selection
228+
229+ if ( dlg . Run ( ) == ( int ) Gtk . ResponseType . Accept )
230+ {
231+ result = dlg . Filenames ;
232+ }
233+ else
234+ {
235+ result = null ; // User canceled the dialog
236+ }
237+ }
238+ finally
239+ {
240+ Gtk . Application . Quit ( ) ; // stops the main loop
241+ }
242+
243+ return Task . FromResult ( result ) ;
244+ }
245+ else
246+ {
247+ // Fallback
248+ return Task . FromResult < string [ ] ? > ( null ) ;
111249 }
112- return [ path ] ;
113250 }
114251}
0 commit comments