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
This commit is contained in:
2026-01-01 06:22:48 -05:00
parent 1e84c6168a
commit 1f096c38dc
254 changed files with 49359 additions and 38457 deletions

View File

@@ -1,147 +1,139 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Maui.ApplicationModel.DataTransfer;
using Microsoft.Maui.Storage;
namespace Microsoft.Maui.Platform.Linux.Services;
/// <summary>
/// Linux share implementation using xdg-open and portal APIs.
/// </summary>
public class ShareService : IShare
{
public async Task RequestAsync(ShareTextRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
public async Task RequestAsync(ShareTextRequest request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
if (!string.IsNullOrEmpty(request.Uri))
{
await OpenUrlAsync(request.Uri);
}
else if (!string.IsNullOrEmpty(request.Text))
{
string text = Uri.EscapeDataString(request.Subject ?? "");
string text2 = Uri.EscapeDataString(request.Text ?? "");
string url = "mailto:?subject=" + text + "&body=" + text2;
await OpenUrlAsync(url);
}
}
// On Linux, we can use mailto: for text sharing or write to a temp file
if (!string.IsNullOrEmpty(request.Uri))
{
// Share as URL
await OpenUrlAsync(request.Uri);
}
else if (!string.IsNullOrEmpty(request.Text))
{
// Try to use email for text sharing
var subject = Uri.EscapeDataString(request.Subject ?? "");
var body = Uri.EscapeDataString(request.Text ?? "");
var mailto = $"mailto:?subject={subject}&body={body}";
await OpenUrlAsync(mailto);
}
}
public async Task RequestAsync(ShareFileRequest request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
if (request.File == null)
{
throw new ArgumentException("File is required", "request");
}
await ShareFileAsync(((FileBase)request.File).FullPath);
}
public async Task RequestAsync(ShareFileRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
public async Task RequestAsync(ShareMultipleFilesRequest request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
if (request.Files == null || !request.Files.Any())
{
throw new ArgumentException("Files are required", "request");
}
foreach (ShareFile file in request.Files)
{
await ShareFileAsync(((FileBase)file).FullPath);
}
}
if (request.File == null)
throw new ArgumentException("File is required", nameof(request));
private async Task OpenUrlAsync(string url)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = "\"" + url + "\"",
UseShellExecute = false,
CreateNoWindow = true
};
using Process process = Process.Start(startInfo);
if (process != null)
{
await process.WaitForExitAsync();
}
}
catch (Exception innerException)
{
throw new InvalidOperationException("Failed to open URL for sharing", innerException);
}
}
await ShareFileAsync(request.File.FullPath);
}
private async Task ShareFileAsync(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException("File not found for sharing", filePath);
}
try
{
if (await TryPortalShareAsync(filePath))
{
return;
}
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = "\"" + Path.GetDirectoryName(filePath) + "\"",
UseShellExecute = false,
CreateNoWindow = true
};
using Process process = Process.Start(startInfo);
if (process != null)
{
await process.WaitForExitAsync();
}
}
catch (Exception innerException)
{
throw new InvalidOperationException("Failed to share file", innerException);
}
}
public async Task RequestAsync(ShareMultipleFilesRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Files == null || !request.Files.Any())
throw new ArgumentException("Files are required", nameof(request));
// Share files one by one or use file manager
foreach (var file in request.Files)
{
await ShareFileAsync(file.FullPath);
}
}
private async Task OpenUrlAsync(string url)
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = $"\"{url}\"",
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
if (process != null)
{
await process.WaitForExitAsync();
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to open URL for sharing", ex);
}
}
private async Task ShareFileAsync(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("File not found for sharing", filePath);
try
{
// Try to use the portal API via gdbus for proper share dialog
var portalResult = await TryPortalShareAsync(filePath);
if (portalResult)
return;
// Fall back to opening with default file manager
var startInfo = new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = $"\"{Path.GetDirectoryName(filePath)}\"",
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
if (process != null)
{
await process.WaitForExitAsync();
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to share file", ex);
}
}
private async Task<bool> TryPortalShareAsync(string filePath)
{
try
{
// Try freedesktop portal for proper share dialog
// This would use org.freedesktop.portal.FileChooser or similar
// For now, we'll use zenity --info as a fallback notification
var startInfo = new ProcessStartInfo
{
FileName = "zenity",
Arguments = $"--info --text=\"File ready to share:\\n{Path.GetFileName(filePath)}\\n\\nPath: {filePath}\" --title=\"Share File\"",
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
if (process != null)
{
await process.WaitForExitAsync();
return true;
}
return false;
}
catch
{
return false;
}
}
private async Task<bool> TryPortalShareAsync(string filePath)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "zenity",
Arguments = $"--info --text=\"File ready to share:\\n{Path.GetFileName(filePath)}\\n\\nPath: {filePath}\" --title=\"Share File\"",
UseShellExecute = false,
CreateNoWindow = true
};
using Process process = Process.Start(startInfo);
if (process != null)
{
await process.WaitForExitAsync();
return true;
}
return false;
}
catch
{
return false;
}
}
}