#if UNITY_STANDALONE_LINUX || CT_DEVELOP using System; using UnityEngine; namespace Crosstales.FB.Wrapper { /// File browser implementation for Linux (GTK). public class FileBrowserLinux : BaseFileBrowserStandalone { #region Variables private static FileBrowserLinux instance; private static Action _openFileCb; private static Action _openFolderCb; private static Action _saveFileCb; private const char splitChar = (char)28; #endregion #region Constructor public FileBrowserLinux() { instance = this; Linux.NativeMethods.DialogInit(); } #endregion #region Implemented methods public override bool canOpenMultipleFolders => true; public override bool isPlatformSupported => Crosstales.FB.Util.Helper.isLinuxPlatform; // || Util.Helper.isLinuxEditor; public override bool isWorkingInEditor => false; public override string[] OpenFiles(string title, string directory, string defaultName, bool multiselect, params ExtensionFilter[] extensions) { if (!string.IsNullOrEmpty(defaultName)) Debug.LogWarning("'defaultName' is not supported under Linux."); //resetOpenFiles(); return openFiles(title, directory, defaultName, multiselect, false, extensions); } public override string[] OpenFolders(string title, string directory, bool multiselect) { //resetOpenFolders(); return openFolders(title, directory, multiselect, false); } public override string SaveFile(string title, string directory, string defaultName, params ExtensionFilter[] extensions) { //resetSaveFile(); return saveFile(title, directory, defaultName, false, extensions); } public override void OpenFilesAsync(string title, string directory, string defaultName, bool multiselect, ExtensionFilter[] extensions, Action cb) { if (!string.IsNullOrEmpty(defaultName)) Debug.LogWarning("'defaultName' is not supported under Linux."); //resetOpenFiles(); _openFileCb = cb; openFiles(title, directory, defaultName, multiselect, true, extensions); } public override void OpenFoldersAsync(string title, string directory, bool multiselect, Action cb) { //resetOpenFolders(); _openFolderCb = cb; openFolders(title, directory, multiselect, true); } public override void SaveFileAsync(string title, string directory, string defaultName, ExtensionFilter[] extensions, Action cb) { //resetSaveFile(); _saveFileCb = cb; saveFile(title, directory, defaultName, true, extensions); } #endregion #region Private methods private static string getFilterFromFileExtensionList(ExtensionFilter[] extensions) { if (extensions?.Length > 0) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); for (int xx = 0; xx < extensions.Length; xx++) { ExtensionFilter filter = extensions[xx]; sb.Append(filter.Name); sb.Append(";"); for (int ii = 0; ii < filter.Extensions.Length; ii++) { sb.Append(filter.Extensions[ii]); if (ii + 1 < filter.Extensions.Length) sb.Append(","); } if (xx + 1 < extensions.Length) sb.Append("|"); } if (Crosstales.FB.Util.Config.DEBUG) Debug.Log($"getFilterFromFileExtensionList: {sb}"); return sb.ToString(); } return string.Empty; } #endregion private static string[] openFiles(string title, string directory, string defaultName, bool multiselect, bool isAsync, params ExtensionFilter[] extensions) { try { if (isAsync) { //TODO change to the same method as for macOS? Linux.NativeMethods.DialogOpenFilePanelAsync(title, directory, getFilterFromFileExtensionList(extensions), multiselect, paths => { if (string.IsNullOrEmpty(paths)) { instance.CurrentOpenFiles = System.Array.Empty(); instance.CurrentOpenSingleFile = string.Empty; _openFileCb?.Invoke(null); } else { string[] pathArray = paths.Split(splitChar); instance.CurrentOpenFiles = pathArray; instance.CurrentOpenSingleFile = pathArray[0]; _openFileCb?.Invoke(pathArray); } }); } else { string paths = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Linux.NativeMethods.DialogOpenFilePanel(title, directory, getFilterFromFileExtensionList(extensions), multiselect)); if (string.IsNullOrEmpty(paths)) { instance.CurrentOpenFiles = System.Array.Empty(); instance.CurrentOpenSingleFile = string.Empty; return null; } string[] pathArray = paths.Split(splitChar); instance.CurrentOpenFiles = pathArray; instance.CurrentOpenSingleFile = pathArray[0]; return instance.CurrentOpenFiles; } } catch (Exception ex) { instance.CurrentOpenFiles = System.Array.Empty(); instance.CurrentOpenSingleFile = string.Empty; Debug.LogError($"Open file dialog threw an error: {ex}"); } return null; } private static string[] openFolders(string title, string directory, bool multiselect, bool isAsync) { try { if (isAsync) { //TODO change to the same method as for macOS? Linux.NativeMethods.DialogOpenFolderPanelAsync(title, directory, multiselect, paths => { if (string.IsNullOrEmpty(paths)) { instance.CurrentOpenFolders = System.Array.Empty(); instance.CurrentOpenSingleFolder = string.Empty; _openFolderCb?.Invoke(null); } else { string[] pathArray = paths.Split(splitChar); instance.CurrentOpenFolders = pathArray; instance.CurrentOpenSingleFolder = pathArray[0]; _openFolderCb?.Invoke(pathArray); } }); } else { string paths = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Linux.NativeMethods.DialogOpenFolderPanel(title, directory, multiselect)); if (string.IsNullOrEmpty(paths)) { instance.CurrentOpenFolders = System.Array.Empty(); instance.CurrentOpenSingleFolder = string.Empty; return null; } string[] pathArray = paths.Split(splitChar); instance.CurrentOpenFolders = pathArray; instance.CurrentOpenSingleFolder = pathArray[0]; return instance.CurrentOpenFolders; } } catch (Exception ex) { instance.CurrentOpenFolders = System.Array.Empty(); instance.CurrentOpenSingleFolder = string.Empty; Debug.LogError($"Folder dialog threw an error: {ex}"); } return null; } private static string saveFile(string title, string directory, string defaultName, bool isAsync, params ExtensionFilter[] extensions) { bool useFallback = extensions != null && extensions.Length == 1 && extensions[0].Extensions.Length == 1; string fallbackExtension = useFallback ? $".{extensions[0].Extensions[0]}" : string.Empty; try { if (isAsync) { //TODO change to the same method as for macOS? Linux.NativeMethods.DialogSaveFilePanelAsync(title, directory, defaultName, getFilterFromFileExtensionList(extensions), path => { if (string.IsNullOrEmpty(path)) { instance.CurrentSaveFile = string.Empty; _saveFileCb?.Invoke(null); } else { if (useFallback && !path.EndsWith(fallbackExtension)) path += fallbackExtension; instance.CurrentSaveFile = path; _saveFileCb?.Invoke(path); } }); } else { string path = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(Linux.NativeMethods.DialogSaveFilePanel(title, directory, defaultName, getFilterFromFileExtensionList(extensions))); if (string.IsNullOrEmpty(path)) { instance.CurrentSaveFile = string.Empty; return null; } if (useFallback && !path.EndsWith(fallbackExtension)) path += fallbackExtension; instance.CurrentSaveFile = path; return instance.CurrentSaveFile; } } catch (Exception ex) { instance.CurrentSaveFile = string.Empty; Debug.LogError($"Save file dialog threw an error: {ex}"); } return null; } } } namespace Crosstales.FB.Wrapper.Linux { /// Native methods (bridge to Linux). internal static class NativeMethods { [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)] public delegate void AsyncCallback(string path); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern void DialogInit(); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern IntPtr DialogOpenFilePanel(string title, string directory, string extension, bool multiselect); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern IntPtr DialogOpenFolderPanel(string title, string directory, bool multiselect); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern IntPtr DialogSaveFilePanel(string title, string directory, string defaultName, string extension); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern void DialogOpenFilePanelAsync(string title, string directory, string extension, bool multiselect, AsyncCallback callback); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern void DialogOpenFolderPanelAsync(string title, string directory, bool multiselect, AsyncCallback callback); [System.Runtime.InteropServices.DllImport("FileBrowser")] internal static extern void DialogSaveFilePanelAsync(string title, string directory, string defaultName, string extension, AsyncCallback callback); } } #endif // © 2019-2023 crosstales LLC (https://www.crosstales.com)