Files
maui-linux/Services/PortalFilePickerService.cs
Dave Friedel 1f096c38dc Update with recovered code from VM binaries (Jan 1)
Recovered from decompiled OpenMaui.Controls.Linux.dll:
- SkiaShell.cs: FlyoutHeader, FlyoutFooter, scroll support (918 -> 1325 lines)
- X11Window.cs: Cursor support (XCreateFontCursor, XDefineCursor)
- All handlers with dark mode support
- All services with latest implementations
- LinuxApplication with theme change handling
2026-01-01 06:22:48 -05:00

385 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.Storage;
namespace Microsoft.Maui.Platform.Linux.Services;
public class PortalFilePickerService : IFilePicker
{
private bool _portalAvailable = true;
private string? _fallbackTool;
public PortalFilePickerService()
{
DetectAvailableTools();
}
private void DetectAvailableTools()
{
_portalAvailable = CheckPortalAvailable();
if (!_portalAvailable)
{
if (IsCommandAvailable("zenity"))
{
_fallbackTool = "zenity";
}
else if (IsCommandAvailable("kdialog"))
{
_fallbackTool = "kdialog";
}
else if (IsCommandAvailable("yad"))
{
_fallbackTool = "yad";
}
}
}
private bool CheckPortalAvailable()
{
try
{
return RunCommand("busctl", "--user list | grep -q org.freedesktop.portal.Desktop && echo yes").Trim() == "yes";
}
catch
{
return false;
}
}
private bool IsCommandAvailable(string command)
{
try
{
return !string.IsNullOrWhiteSpace(RunCommand("which", command));
}
catch
{
return false;
}
}
public async Task<FileResult?> PickAsync(PickOptions? options = null)
{
if (options == null)
{
options = new PickOptions();
}
return (await PickFilesAsync(options, allowMultiple: false)).FirstOrDefault();
}
public async Task<IEnumerable<FileResult>> PickMultipleAsync(PickOptions? options = null)
{
if (options == null)
{
options = new PickOptions();
}
return await PickFilesAsync(options, allowMultiple: true);
}
private async Task<IEnumerable<FileResult>> PickFilesAsync(PickOptions options, bool allowMultiple)
{
if (_portalAvailable)
{
return await PickWithPortalAsync(options, allowMultiple);
}
if (_fallbackTool != null)
{
return await PickWithFallbackAsync(options, allowMultiple);
}
Console.WriteLine("[FilePickerService] No file picker available (install xdg-desktop-portal, zenity, or kdialog)");
return Enumerable.Empty<FileResult>();
}
private async Task<IEnumerable<FileResult>> PickWithPortalAsync(PickOptions options, bool allowMultiple)
{
IEnumerable<FileResult> result = default(IEnumerable<FileResult>);
object obj;
int num;
try
{
string text = BuildPortalFilterArgs(options.FileTypes);
string value = (allowMultiple ? "true" : "false");
string input = options.PickerTitle ?? "Open File";
StringBuilder args = new StringBuilder();
args.Append("call --session ");
args.Append("--dest org.freedesktop.portal.Desktop ");
args.Append("--object-path /org/freedesktop/portal/desktop ");
args.Append("--method org.freedesktop.portal.FileChooser.OpenFile ");
args.Append("\"\" ");
StringBuilder stringBuilder = args;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(3, 1, stringBuilder);
handler.AppendLiteral("\"");
handler.AppendFormatted(EscapeForShell(input));
handler.AppendLiteral("\" ");
stringBuilder2.Append(ref handler);
args.Append("@a{sv} {");
stringBuilder = args;
StringBuilder stringBuilder3 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder);
handler.AppendLiteral("'multiple': <");
handler.AppendFormatted(value);
handler.AppendLiteral(">");
stringBuilder3.Append(ref handler);
if (text != null)
{
stringBuilder = args;
StringBuilder stringBuilder4 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(15, 1, stringBuilder);
handler.AppendLiteral(", 'filters': <");
handler.AppendFormatted(text);
handler.AppendLiteral(">");
stringBuilder4.Append(ref handler);
}
args.Append("}");
if (string.IsNullOrEmpty(ParseRequestPath(await Task.Run(() => RunCommand("gdbus", args.ToString())))))
{
result = Enumerable.Empty<FileResult>();
return result;
}
await Task.Delay(100);
if (_fallbackTool != null)
{
result = await PickWithFallbackAsync(options, allowMultiple);
return result;
}
result = Enumerable.Empty<FileResult>();
return result;
}
catch (Exception ex)
{
obj = ex;
num = 1;
}
if (num != 1)
{
return result;
}
Exception ex2 = (Exception)obj;
Console.WriteLine("[FilePickerService] Portal error: " + ex2.Message);
if (_fallbackTool != null)
{
return await PickWithFallbackAsync(options, allowMultiple);
}
return Enumerable.Empty<FileResult>();
}
private async Task<IEnumerable<FileResult>> PickWithFallbackAsync(PickOptions options, bool allowMultiple)
{
return _fallbackTool switch
{
"zenity" => await PickWithZenityAsync(options, allowMultiple),
"kdialog" => await PickWithKdialogAsync(options, allowMultiple),
"yad" => await PickWithYadAsync(options, allowMultiple),
_ => Enumerable.Empty<FileResult>(),
};
}
private async Task<IEnumerable<FileResult>> PickWithZenityAsync(PickOptions options, bool allowMultiple)
{
StringBuilder args = new StringBuilder();
args.Append("--file-selection ");
if (!string.IsNullOrEmpty(options.PickerTitle))
{
StringBuilder stringBuilder = args;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder);
handler.AppendLiteral("--title=\"");
handler.AppendFormatted(EscapeForShell(options.PickerTitle));
handler.AppendLiteral("\" ");
stringBuilder2.Append(ref handler);
}
if (allowMultiple)
{
args.Append("--multiple --separator=\"|\" ");
}
List<string> extensionsFromFileType = GetExtensionsFromFileType(options.FileTypes);
if (extensionsFromFileType.Count > 0)
{
string value = string.Join(" ", extensionsFromFileType.Select((string e) => "*" + e));
StringBuilder stringBuilder = args;
StringBuilder stringBuilder3 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(25, 1, stringBuilder);
handler.AppendLiteral("--file-filter=\"Files | ");
handler.AppendFormatted(value);
handler.AppendLiteral("\" ");
stringBuilder3.Append(ref handler);
}
string text = await Task.Run(() => RunCommand("zenity", args.ToString()));
if (string.IsNullOrWhiteSpace(text))
{
return Enumerable.Empty<FileResult>();
}
return ((IEnumerable<string>)text.Trim().Split('|', StringSplitOptions.RemoveEmptyEntries)).Select((Func<string, FileResult>)((string f) => new FileResult(f.Trim()))).ToList();
}
private async Task<IEnumerable<FileResult>> PickWithKdialogAsync(PickOptions options, bool allowMultiple)
{
StringBuilder args = new StringBuilder();
args.Append("--getopenfilename ");
args.Append(". ");
List<string> extensionsFromFileType = GetExtensionsFromFileType(options.FileTypes);
if (extensionsFromFileType.Count > 0)
{
string value = string.Join(" ", extensionsFromFileType.Select((string e) => "*" + e));
StringBuilder stringBuilder = args;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder);
handler.AppendLiteral("\"Files (");
handler.AppendFormatted(value);
handler.AppendLiteral(")\" ");
stringBuilder2.Append(ref handler);
}
if (!string.IsNullOrEmpty(options.PickerTitle))
{
StringBuilder stringBuilder = args;
StringBuilder stringBuilder3 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder);
handler.AppendLiteral("--title \"");
handler.AppendFormatted(EscapeForShell(options.PickerTitle));
handler.AppendLiteral("\" ");
stringBuilder3.Append(ref handler);
}
if (allowMultiple)
{
args.Append("--multiple --separate-output ");
}
string text = await Task.Run(() => RunCommand("kdialog", args.ToString()));
if (string.IsNullOrWhiteSpace(text))
{
return Enumerable.Empty<FileResult>();
}
return ((IEnumerable<string>)text.Trim().Split('\n', StringSplitOptions.RemoveEmptyEntries)).Select((Func<string, FileResult>)((string f) => new FileResult(f.Trim()))).ToList();
}
private async Task<IEnumerable<FileResult>> PickWithYadAsync(PickOptions options, bool allowMultiple)
{
StringBuilder args = new StringBuilder();
args.Append("--file ");
if (!string.IsNullOrEmpty(options.PickerTitle))
{
StringBuilder stringBuilder = args;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder);
handler.AppendLiteral("--title=\"");
handler.AppendFormatted(EscapeForShell(options.PickerTitle));
handler.AppendLiteral("\" ");
stringBuilder2.Append(ref handler);
}
if (allowMultiple)
{
args.Append("--multiple --separator=\"|\" ");
}
List<string> extensionsFromFileType = GetExtensionsFromFileType(options.FileTypes);
if (extensionsFromFileType.Count > 0)
{
string value = string.Join(" ", extensionsFromFileType.Select((string e) => "*" + e));
StringBuilder stringBuilder = args;
StringBuilder stringBuilder3 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(25, 1, stringBuilder);
handler.AppendLiteral("--file-filter=\"Files | ");
handler.AppendFormatted(value);
handler.AppendLiteral("\" ");
stringBuilder3.Append(ref handler);
}
string text = await Task.Run(() => RunCommand("yad", args.ToString()));
if (string.IsNullOrWhiteSpace(text))
{
return Enumerable.Empty<FileResult>();
}
return ((IEnumerable<string>)text.Trim().Split('|', StringSplitOptions.RemoveEmptyEntries)).Select((Func<string, FileResult>)((string f) => new FileResult(f.Trim()))).ToList();
}
private List<string> GetExtensionsFromFileType(FilePickerFileType? fileType)
{
List<string> list = new List<string>();
if (fileType == null)
{
return list;
}
try
{
IEnumerable<string> value = fileType.Value;
if (value == null)
{
return list;
}
foreach (string item2 in value)
{
if (item2.StartsWith(".") || (!item2.Contains('/') && !item2.Contains('*')))
{
string item = (item2.StartsWith(".") ? item2 : ("." + item2));
if (!list.Contains(item))
{
list.Add(item);
}
}
}
}
catch
{
}
return list;
}
private string? BuildPortalFilterArgs(FilePickerFileType? fileType)
{
List<string> extensionsFromFileType = GetExtensionsFromFileType(fileType);
if (extensionsFromFileType.Count == 0)
{
return null;
}
string text = string.Join(", ", extensionsFromFileType.Select((string e) => "(uint32 0, '*" + e + "')"));
return "[('Files', [" + text + "])]";
}
private string? ParseRequestPath(string output)
{
int num = output.IndexOf("'/");
int num2 = output.IndexOf("',", num);
if (num >= 0 && num2 > num)
{
return output.Substring(num + 1, num2 - num - 1);
}
return null;
}
private string EscapeForShell(string input)
{
return input.Replace("\"", "\\\"").Replace("'", "\\'");
}
private string RunCommand(string command, string arguments)
{
try
{
using Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = command,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit(30000);
return result;
}
catch (Exception ex)
{
Console.WriteLine("[FilePickerService] Command error: " + ex.Message);
return "";
}
}
}