commit bb07ec43cd45f5063866f35080c833fdb25b3346 Author: milleniumbug Date: Sun Dec 26 21:04:24 2021 +0100 First release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d647b48 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "nativefiledialog"] + path = nativefiledialog + url = https://github.com/milleniumbug/nativefiledialog diff --git a/NativeFileDialogSharp.sln b/NativeFileDialogSharp.sln new file mode 100644 index 0000000..89a69db --- /dev/null +++ b/NativeFileDialogSharp.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeFileDialogSharp", "NativeFileDialogSharp\NativeFileDialogSharp.csproj", "{4127F279-9FD5-4C37-B904-242C124C1A07}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeFileDialogSharpSandbox", "NativeFileDialogSharpSandbox\NativeFileDialogSharpSandbox.csproj", "{427E5F76-8418-4EA3-9AA3-C1DBFDE0478F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4127F279-9FD5-4C37-B904-242C124C1A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4127F279-9FD5-4C37-B904-242C124C1A07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4127F279-9FD5-4C37-B904-242C124C1A07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4127F279-9FD5-4C37-B904-242C124C1A07}.Release|Any CPU.Build.0 = Release|Any CPU + {427E5F76-8418-4EA3-9AA3-C1DBFDE0478F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {427E5F76-8418-4EA3-9AA3-C1DBFDE0478F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {427E5F76-8418-4EA3-9AA3-C1DBFDE0478F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {427E5F76-8418-4EA3-9AA3-C1DBFDE0478F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/NativeFileDialogSharp/Native/NativeFunctions.cs b/NativeFileDialogSharp/Native/NativeFunctions.cs new file mode 100644 index 0000000..e332cdc --- /dev/null +++ b/NativeFileDialogSharp/Native/NativeFunctions.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.InteropServices; + +namespace NativeFileDialogSharp.Native; + +public struct nfdpathset_t +{ + public IntPtr buf; + public IntPtr indices; + public UIntPtr count; +} + +public enum nfdresult_t +{ + NFD_ERROR, + NFD_OKAY, + NFD_CANCEL +} + +public static class NativeFunctions +{ + public const string LibraryName = "nfd"; + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe nfdresult_t NFD_OpenDialog(byte* filterList, byte* defaultPath, out IntPtr outPath); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe nfdresult_t NFD_OpenDialogMultiple(byte* filterList, byte* defaultPath, nfdpathset_t* outPaths); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe nfdresult_t NFD_SaveDialog(byte* filterList, byte* defaultPath, out IntPtr outPath); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe nfdresult_t NFD_PickFolder(byte* defaultPath, out IntPtr outPath); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe byte* NFD_GetError(); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe UIntPtr NFD_PathSet_GetCount(nfdpathset_t* pathSet); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe byte* NFD_PathSet_GetPath(nfdpathset_t* pathSet, UIntPtr index); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe void NFD_PathSet_Free(nfdpathset_t* pathSet); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe void NFD_Dummy(); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe IntPtr NFD_Malloc(UIntPtr bytes); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern unsafe void NFD_Free(IntPtr ptr); +} \ No newline at end of file diff --git a/NativeFileDialogSharp/NativeFileDialogSharp.csproj b/NativeFileDialogSharp/NativeFileDialogSharp.csproj new file mode 100644 index 0000000..793d188 --- /dev/null +++ b/NativeFileDialogSharp/NativeFileDialogSharp.csproj @@ -0,0 +1,30 @@ + + + + 10 + 0.1 + net6.0 + disable + + + + true + + + + true + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/NativeFileDialogSharp/NativeWrappers.cs b/NativeFileDialogSharp/NativeWrappers.cs new file mode 100644 index 0000000..d3c47a2 --- /dev/null +++ b/NativeFileDialogSharp/NativeWrappers.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NativeFileDialogSharp.Native; + +public static class Dialog +{ + private static byte[] ToUtf8(string s) + { + var byteCount = Encoding.UTF8.GetByteCount(s); + var bytes = new byte[byteCount + 1]; + Encoding.UTF8.GetBytes(s.AsSpan(), bytes.AsSpan()); + return bytes; + } + + private static unsafe Span MakeSpanFromNullTerminatedString(byte* nullTerminatedString) + { + int count = 0; + var ptr = nullTerminatedString; + while (*ptr != 0) + { + ptr++; + count++; + } + + return new Span(nullTerminatedString, count); + } + + private static string FromUtf8(ReadOnlySpan input) + { + return Encoding.UTF8.GetString(input); + } + + public static unsafe DialogResult FileOpen(string filterList = null, string defaultPath = null) + { + fixed (byte* filterListNts = filterList != null ? ToUtf8(filterList) : null) + fixed (byte* defaultPathNts = defaultPath != null ? ToUtf8(defaultPath) : null) + { + string path = null; + string errorMessage = null; + var result = NativeFunctions.NFD_OpenDialog(filterListNts, defaultPathNts, out IntPtr outPathIntPtr); + if (result == nfdresult_t.NFD_ERROR) + { + errorMessage = FromUtf8(MakeSpanFromNullTerminatedString(NativeFunctions.NFD_GetError())); + } + else if (result == nfdresult_t.NFD_OKAY) + { + var outPathNts = (byte*)outPathIntPtr.ToPointer(); + path = FromUtf8(MakeSpanFromNullTerminatedString(outPathNts)); + NativeFunctions.NFD_Free(outPathIntPtr); + } + + return new DialogResult(result, path, null, errorMessage); + } + } + + public static unsafe DialogResult FileSave(string filterList = null, string defaultPath = null) + { + fixed (byte* filterListNts = filterList != null ? ToUtf8(filterList) : null) + fixed (byte* defaultPathNts = defaultPath != null ? ToUtf8(defaultPath) : null) + { + string path = null; + string errorMessage = null; + var result = NativeFunctions.NFD_SaveDialog(filterListNts, defaultPathNts, out IntPtr outPathIntPtr); + if (result == nfdresult_t.NFD_ERROR) + { + errorMessage = FromUtf8(MakeSpanFromNullTerminatedString(NativeFunctions.NFD_GetError())); + } + else if (result == nfdresult_t.NFD_OKAY) + { + var outPathNts = (byte*)outPathIntPtr.ToPointer(); + path = FromUtf8(MakeSpanFromNullTerminatedString(outPathNts)); + NativeFunctions.NFD_Free(outPathIntPtr); + } + + return new DialogResult(result, path, null, errorMessage); + } + } + + public static unsafe DialogResult FolderPicker(string defaultPath = null) + { + fixed (byte* defaultPathNts = defaultPath != null ? ToUtf8(defaultPath) : null) + { + string path = null; + string errorMessage = null; + var result = NativeFunctions.NFD_PickFolder(defaultPathNts, out IntPtr outPathIntPtr); + if (result == nfdresult_t.NFD_ERROR) + { + errorMessage = FromUtf8(MakeSpanFromNullTerminatedString(NativeFunctions.NFD_GetError())); + } + else if (result == nfdresult_t.NFD_OKAY) + { + var outPathNts = (byte*)outPathIntPtr.ToPointer(); + path = FromUtf8(MakeSpanFromNullTerminatedString(outPathNts)); + NativeFunctions.NFD_Free(outPathIntPtr); + } + + return new DialogResult(result, path, null, errorMessage); + } + } + + public static unsafe DialogResult FileOpenMultiple(string filterList = null, string defaultPath = null) + { + fixed (byte* filterListNts = filterList != null ? ToUtf8(filterList) : null) + fixed (byte* defaultPathNts = defaultPath != null ? ToUtf8(defaultPath) : null) + { + List paths = null; + string errorMessage = null; + nfdpathset_t pathSet; + var result = NativeFunctions.NFD_OpenDialogMultiple(filterListNts, defaultPathNts, &pathSet); + if (result == nfdresult_t.NFD_ERROR) + { + errorMessage = FromUtf8(MakeSpanFromNullTerminatedString(NativeFunctions.NFD_GetError())); + } + else if (result == nfdresult_t.NFD_OKAY) + { + var pathCount = (int)NativeFunctions.NFD_PathSet_GetCount(&pathSet).ToUInt32(); + paths = new List(pathCount); + for (int i = 0; i < pathCount; i++) + { + paths.Add(FromUtf8(MakeSpanFromNullTerminatedString(NativeFunctions.NFD_PathSet_GetPath(&pathSet, new UIntPtr((uint)i))))); + } + NativeFunctions.NFD_PathSet_Free(&pathSet); + } + + return new DialogResult(result, null, paths, errorMessage); + } + } +} + +public class DialogResult +{ + private readonly nfdresult_t result; + + public string Path { get; } + + public IReadOnlyList Paths { get; } + + public bool IsError => result == nfdresult_t.NFD_ERROR; + + public string ErrorMessage { get; } + + public bool IsCancelled => result == nfdresult_t.NFD_CANCEL; + + public bool IsOk => result == nfdresult_t.NFD_OKAY; + + internal DialogResult(nfdresult_t result, string path, IReadOnlyList paths, string errorMessage) + { + this.result = result; + Path = path; + Paths = paths; + ErrorMessage = errorMessage; + } +} \ No newline at end of file diff --git a/NativeFileDialogSharp/libnfd.so b/NativeFileDialogSharp/libnfd.so new file mode 100755 index 0000000..b32d2f2 Binary files /dev/null and b/NativeFileDialogSharp/libnfd.so differ diff --git a/NativeFileDialogSharp/nfd.dll b/NativeFileDialogSharp/nfd.dll new file mode 100644 index 0000000..50acdbd Binary files /dev/null and b/NativeFileDialogSharp/nfd.dll differ diff --git a/NativeFileDialogSharp/nfd.pdb b/NativeFileDialogSharp/nfd.pdb new file mode 100644 index 0000000..a859ff6 Binary files /dev/null and b/NativeFileDialogSharp/nfd.pdb differ diff --git a/NativeFileDialogSharpSandbox/NativeFileDialogSharpSandbox.csproj b/NativeFileDialogSharpSandbox/NativeFileDialogSharpSandbox.csproj new file mode 100644 index 0000000..b6022b1 --- /dev/null +++ b/NativeFileDialogSharpSandbox/NativeFileDialogSharpSandbox.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/NativeFileDialogSharpSandbox/Program.cs b/NativeFileDialogSharpSandbox/Program.cs new file mode 100644 index 0000000..1913306 --- /dev/null +++ b/NativeFileDialogSharpSandbox/Program.cs @@ -0,0 +1,12 @@ +// See https://aka.ms/new-console-template for more information + +using NativeFileDialogSharp.Native; + +var result = Dialog.FileOpenMultiple(); + +Console.WriteLine($"Path: {result.Path}, IsError {result.IsError}, IsOk {result.IsOk}, IsCancelled {result.IsCancelled}, ErrorMessage {result.ErrorMessage}"); +if (result.Paths != null) +{ + Console.WriteLine("Paths"); + Console.WriteLine(string.Join("\n", result.Paths)); +} \ No newline at end of file diff --git a/nativefiledialog b/nativefiledialog new file mode 160000 index 0000000..5018af9 --- /dev/null +++ b/nativefiledialog @@ -0,0 +1 @@ +Subproject commit 5018af9ba752cafeaed10b1ceba1b07dcb551923