Verify Views files against decompiled, extract embedded types

Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements

Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment

Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs

Build: 0 errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 17:02:39 -05:00
parent 6007b84e7a
commit f6eadaad57
171 changed files with 4208 additions and 3913 deletions

52
Hosting/GtkMauiContext.cs Normal file
View File

@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Animations;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Platform.Linux.Dispatching;
namespace Microsoft.Maui.Platform.Linux.Hosting;
public class GtkMauiContext : IMauiContext
{
private readonly IServiceProvider _services;
private readonly IMauiHandlersFactory _handlers;
private IAnimationManager? _animationManager;
private IDispatcher? _dispatcher;
public IServiceProvider Services => _services;
public IMauiHandlersFactory Handlers => _handlers;
public IAnimationManager AnimationManager
{
get
{
_animationManager ??= _services.GetService<IAnimationManager>()
?? new LinuxAnimationManager(new LinuxTicker());
return _animationManager;
}
}
public IDispatcher Dispatcher
{
get
{
_dispatcher ??= _services.GetService<IDispatcher>()
?? new LinuxDispatcher();
return _dispatcher;
}
}
public GtkMauiContext(IServiceProvider services)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
if (LinuxApplication.Current == null)
{
new LinuxApplication();
}
}
}

View File

@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Maui.Hosting;
namespace Microsoft.Maui.Platform.Linux.Hosting;
public static class HandlerMappingExtensions
{
public static IMauiHandlersCollection AddHandler<TView, THandler>(this IMauiHandlersCollection handlers)
where TView : class
where THandler : class
{
handlers.AddHandler(typeof(TView), typeof(THandler));
return handlers;
}
}

View File

@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Maui.Animations;
using Animation = Microsoft.Maui.Animations.Animation;
namespace Microsoft.Maui.Platform.Linux.Hosting;
internal class LinuxAnimationManager : IAnimationManager
{
private readonly List<Animation> _animations = new();
private readonly ITicker _ticker;
public double SpeedModifier { get; set; } = 1.0;
public bool AutoStartTicker { get; set; } = true;
public ITicker Ticker => _ticker;
public LinuxAnimationManager(ITicker ticker)
{
_ticker = ticker;
_ticker.Fire = OnTickerFire;
}
public void Add(Animation animation)
{
_animations.Add(animation);
if (AutoStartTicker && !_ticker.IsRunning)
{
_ticker.Start();
}
}
public void Remove(Animation animation)
{
_animations.Remove(animation);
if (_animations.Count == 0 && _ticker.IsRunning)
{
_ticker.Stop();
}
}
private void OnTickerFire()
{
var animationsArray = _animations.ToArray();
foreach (var animation in animationsArray)
{
animation.Tick(0.016 * SpeedModifier);
if (animation.HasFinished)
{
Remove(animation);
}
}
}
}

View File

@@ -7,37 +7,41 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.ApplicationModel.Communication;
using Microsoft.Maui.ApplicationModel.DataTransfer;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Services;
using Microsoft.Maui.Platform.Linux.Converters;
using Microsoft.Maui.Storage;
using Microsoft.Maui.Platform.Linux.Handlers;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Networking;
using Microsoft.Maui.Platform.Linux.Converters;
using Microsoft.Maui.Platform.Linux.Dispatching;
using Microsoft.Maui.Platform.Linux.Handlers;
using Microsoft.Maui.Platform.Linux.Services;
using Microsoft.Maui.Storage;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Hosting;
/// <summary>
/// Extension methods for configuring MAUI applications for Linux.
/// </summary>
public static class LinuxMauiAppBuilderExtensions
{
/// <summary>
/// Configures the MAUI application to run on Linux.
/// </summary>
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder)
{
return builder.UseLinux(configure: null);
return builder.UseLinux(null);
}
/// <summary>
/// Configures the MAUI application to run on Linux with options.
/// </summary>
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder, Action<LinuxApplicationOptions>? configure)
{
var options = new LinuxApplicationOptions();
configure?.Invoke(options);
// Register dispatcher provider
builder.Services.TryAddSingleton<IDispatcherProvider>(LinuxDispatcherProvider.Instance);
// Register device services
builder.Services.TryAddSingleton<IDeviceInfo>(DeviceInfoService.Instance);
builder.Services.TryAddSingleton<IDeviceDisplay>(DeviceDisplayService.Instance);
builder.Services.TryAddSingleton<IAppInfo>(AppInfoService.Instance);
builder.Services.TryAddSingleton<IConnectivity>(ConnectivityService.Instance);
// Register platform services
builder.Services.TryAddSingleton<ILauncher, LauncherService>();
builder.Services.TryAddSingleton<IPreferences, PreferencesService>();
@@ -50,6 +54,9 @@ public static class LinuxMauiAppBuilderExtensions
builder.Services.TryAddSingleton<IBrowser, BrowserService>();
builder.Services.TryAddSingleton<IEmail, EmailService>();
// Register GTK host service
builder.Services.TryAddSingleton(_ => GtkHostService.Instance);
// Register type converters for XAML support
RegisterTypeConverters();
@@ -98,8 +105,8 @@ public static class LinuxMauiAppBuilderExtensions
handlers.AddHandler<ImageButton, ImageButtonHandler>();
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
// Web
handlers.AddHandler<WebView, WebViewHandler>();
// Web - use GtkWebViewHandler
handlers.AddHandler<WebView, GtkWebViewHandler>();
// Collection Views
handlers.AddHandler<CollectionView, CollectionViewHandler>();
@@ -124,33 +131,11 @@ public static class LinuxMauiAppBuilderExtensions
return builder;
}
/// <summary>
/// Registers custom type converters for Linux platform.
/// </summary>
private static void RegisterTypeConverters()
{
// Register SkiaSharp type converters for XAML styling support
TypeDescriptor.AddAttributes(typeof(SKColor), new TypeConverterAttribute(typeof(SKColorTypeConverter)));
TypeDescriptor.AddAttributes(typeof(SKRect), new TypeConverterAttribute(typeof(SKRectTypeConverter)));
TypeDescriptor.AddAttributes(typeof(SKSize), new TypeConverterAttribute(typeof(SKSizeTypeConverter)));
TypeDescriptor.AddAttributes(typeof(SKPoint), new TypeConverterAttribute(typeof(SKPointTypeConverter)));
}
}
/// <summary>
/// Handler registration extensions.
/// </summary>
public static class HandlerMappingExtensions
{
/// <summary>
/// Adds a handler for the specified view type.
/// </summary>
public static IMauiHandlersCollection AddHandler<TView, THandler>(
this IMauiHandlersCollection handlers)
where TView : class
where THandler : class
{
handlers.AddHandler(typeof(TView), typeof(THandler));
return handlers;
}
}

View File

@@ -4,16 +4,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Animations;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Platform;
using Microsoft.Maui.Platform.Linux.Dispatching;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Hosting;
/// <summary>
/// Linux-specific implementation of IMauiContext.
/// Provides the infrastructure for creating handlers and accessing platform services.
/// </summary>
public class LinuxMauiContext : IMauiContext
{
private readonly IServiceProvider _services;
@@ -22,27 +16,12 @@ public class LinuxMauiContext : IMauiContext
private IAnimationManager? _animationManager;
private IDispatcher? _dispatcher;
public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
}
/// <inheritdoc />
public IServiceProvider Services => _services;
/// <inheritdoc />
public IMauiHandlersFactory Handlers => _handlers;
/// <summary>
/// Gets the Linux application instance.
/// </summary>
public LinuxApplication LinuxApp => _linuxApp;
/// <summary>
/// Gets the animation manager.
/// </summary>
public IAnimationManager AnimationManager
{
get
@@ -53,9 +32,6 @@ public class LinuxMauiContext : IMauiContext
}
}
/// <summary>
/// Gets the dispatcher for UI thread operations.
/// </summary>
public IDispatcher Dispatcher
{
get
@@ -65,117 +41,11 @@ public class LinuxMauiContext : IMauiContext
return _dispatcher;
}
}
}
/// <summary>
/// Scoped MAUI context for a specific window or view hierarchy.
/// </summary>
public class ScopedLinuxMauiContext : IMauiContext
{
private readonly LinuxMauiContext _parent;
public ScopedLinuxMauiContext(LinuxMauiContext parent)
public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp)
{
_parent = parent ?? throw new ArgumentNullException(nameof(parent));
}
public IServiceProvider Services => _parent.Services;
public IMauiHandlersFactory Handlers => _parent.Handlers;
}
/// <summary>
/// Linux animation manager.
/// </summary>
internal class LinuxAnimationManager : IAnimationManager
{
private readonly List<Microsoft.Maui.Animations.Animation> _animations = new();
private readonly ITicker _ticker;
public LinuxAnimationManager(ITicker ticker)
{
_ticker = ticker;
_ticker.Fire = OnTickerFire;
}
public double SpeedModifier { get; set; } = 1.0;
public bool AutoStartTicker { get; set; } = true;
public ITicker Ticker => _ticker;
public void Add(Microsoft.Maui.Animations.Animation animation)
{
_animations.Add(animation);
if (AutoStartTicker && !_ticker.IsRunning)
{
_ticker.Start();
}
}
public void Remove(Microsoft.Maui.Animations.Animation animation)
{
_animations.Remove(animation);
if (_animations.Count == 0 && _ticker.IsRunning)
{
_ticker.Stop();
}
}
private void OnTickerFire()
{
var animations = _animations.ToArray();
foreach (var animation in animations)
{
animation.Tick(16.0 / 1000.0 * SpeedModifier); // ~60fps
if (animation.HasFinished)
{
Remove(animation);
}
}
}
}
/// <summary>
/// Linux ticker for animation timing.
/// </summary>
internal class LinuxTicker : ITicker
{
private Timer? _timer;
private bool _isRunning;
private int _maxFps = 60;
public bool IsRunning => _isRunning;
public bool SystemEnabled => true;
public int MaxFps
{
get => _maxFps;
set => _maxFps = Math.Max(1, Math.Min(120, value));
}
public Action? Fire { get; set; }
public void Start()
{
if (_isRunning)
return;
_isRunning = true;
var interval = TimeSpan.FromMilliseconds(1000.0 / _maxFps);
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, interval);
}
public void Stop()
{
_isRunning = false;
_timer?.Dispose();
_timer = null;
}
private void OnTimerCallback(object? state)
{
Fire?.Invoke();
_services = services ?? throw new ArgumentNullException(nameof(services));
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
}
}

View File

@@ -2,9 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Platform;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Services;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Hosting;
@@ -44,6 +45,10 @@ public static class LinuxProgramHost
?? new LinuxApplicationOptions();
ParseCommandLineOptions(args, options);
// Initialize GTK for WebView support
GtkHostService.Instance.Initialize(options.Title, options.Width, options.Height);
Console.WriteLine("[LinuxProgramHost] GTK initialized for WebView support");
// Create Linux application
using var linuxApp = new LinuxApplication();
linuxApp.Initialize(options);

47
Hosting/LinuxTicker.cs Normal file
View File

@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Maui.Animations;
namespace Microsoft.Maui.Platform.Linux.Hosting;
internal class LinuxTicker : ITicker
{
private Timer? _timer;
private bool _isRunning;
private int _maxFps = 60;
public bool IsRunning => _isRunning;
public bool SystemEnabled => true;
public int MaxFps
{
get => _maxFps;
set => _maxFps = Math.Max(1, Math.Min(120, value));
}
public Action? Fire { get; set; }
public void Start()
{
if (!_isRunning)
{
_isRunning = true;
var period = TimeSpan.FromMilliseconds(1000.0 / _maxFps);
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, period);
}
}
public void Stop()
{
_isRunning = false;
_timer?.Dispose();
_timer = null;
}
private void OnTimerCallback(object? state)
{
Fire?.Invoke();
}
}

View File

@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using SkiaSharp;
@@ -198,9 +200,28 @@ public class LinuxViewRenderer
FlyoutBehavior.Locked => ShellFlyoutBehavior.Locked,
FlyoutBehavior.Disabled => ShellFlyoutBehavior.Disabled,
_ => ShellFlyoutBehavior.Flyout
}
},
MauiShell = shell
};
// Apply shell colors based on theme
ApplyShellColors(skiaShell, shell);
// Render flyout header if present
if (shell.FlyoutHeader is View headerView)
{
var skiaHeader = RenderView(headerView);
if (skiaHeader != null)
{
skiaShell.FlyoutHeaderView = skiaHeader;
skiaShell.FlyoutHeaderHeight = (float)(headerView.HeightRequest > 0 ? headerView.HeightRequest : 140.0);
}
}
// Set flyout footer with version info
var version = Assembly.GetEntryAssembly()?.GetName().Version;
skiaShell.FlyoutFooterText = $"Version {version?.Major ?? 1}.{version?.Minor ?? 0}.{version?.Build ?? 0}";
// Process shell items into sections
foreach (var item in shell.Items)
{
@@ -210,6 +231,10 @@ public class LinuxViewRenderer
// Store reference to SkiaShell for navigation
CurrentSkiaShell = skiaShell;
// Set up content renderer and color refresher delegates
skiaShell.ContentRenderer = CreateShellContentPage;
skiaShell.ColorRefresher = ApplyShellColors;
// Subscribe to MAUI Shell navigation events to update SkiaShell
shell.Navigated += OnShellNavigated;
shell.Navigating += (s, e) => Console.WriteLine($"[Navigation] Navigating: {e.Target}");
@@ -223,6 +248,61 @@ public class LinuxViewRenderer
return skiaShell;
}
/// <summary>
/// Applies shell colors based on the current theme (dark/light mode).
/// </summary>
private static void ApplyShellColors(SkiaShell skiaShell, Shell shell)
{
bool isDark = Application.Current?.UserAppTheme == AppTheme.Dark;
Console.WriteLine($"[ApplyShellColors] Theme is: {(isDark ? "Dark" : "Light")}");
// Flyout background color
if (shell.FlyoutBackgroundColor != null && shell.FlyoutBackgroundColor != Colors.Transparent)
{
var color = shell.FlyoutBackgroundColor;
skiaShell.FlyoutBackgroundColor = new SKColor(
(byte)(color.Red * 255f),
(byte)(color.Green * 255f),
(byte)(color.Blue * 255f),
(byte)(color.Alpha * 255f));
Console.WriteLine($"[ApplyShellColors] FlyoutBackgroundColor from MAUI: {skiaShell.FlyoutBackgroundColor}");
}
else
{
skiaShell.FlyoutBackgroundColor = isDark
? new SKColor(30, 30, 30)
: new SKColor(255, 255, 255);
Console.WriteLine($"[ApplyShellColors] Using default FlyoutBackgroundColor: {skiaShell.FlyoutBackgroundColor}");
}
// Flyout text color
skiaShell.FlyoutTextColor = isDark
? new SKColor(224, 224, 224)
: new SKColor(33, 33, 33);
Console.WriteLine($"[ApplyShellColors] FlyoutTextColor: {skiaShell.FlyoutTextColor}");
// Content background color
skiaShell.ContentBackgroundColor = isDark
? new SKColor(18, 18, 18)
: new SKColor(250, 250, 250);
Console.WriteLine($"[ApplyShellColors] ContentBackgroundColor: {skiaShell.ContentBackgroundColor}");
// NavBar background color
if (shell.BackgroundColor != null && shell.BackgroundColor != Colors.Transparent)
{
var color = shell.BackgroundColor;
skiaShell.NavBarBackgroundColor = new SKColor(
(byte)(color.Red * 255f),
(byte)(color.Green * 255f),
(byte)(color.Blue * 255f),
(byte)(color.Alpha * 255f));
}
else
{
skiaShell.NavBarBackgroundColor = new SKColor(33, 150, 243); // Material blue
}
}
/// <summary>
/// Handles MAUI Shell navigation events and updates SkiaShell accordingly.
/// </summary>
@@ -290,7 +370,8 @@ public class LinuxViewRenderer
var shellContent = new ShellContent
{
Title = content.Title ?? shellSection.Title ?? flyoutItem.Title ?? "",
Route = content.Route ?? ""
Route = content.Route ?? "",
MauiShellContent = content
};
// Create the page content
@@ -328,7 +409,8 @@ public class LinuxViewRenderer
var shellContent = new ShellContent
{
Title = content.Title ?? tab.Title ?? "",
Route = content.Route ?? ""
Route = content.Route ?? "",
MauiShellContent = content
};
var pageContent = CreateShellContentPage(content);
@@ -359,7 +441,8 @@ public class LinuxViewRenderer
var shellContent = new ShellContent
{
Title = content.Title ?? "",
Route = content.Route ?? ""
Route = content.Route ?? "",
MauiShellContent = content
};
var pageContent = CreateShellContentPage(content);
@@ -402,17 +485,38 @@ public class LinuxViewRenderer
var contentView = RenderView(cp.Content);
if (contentView != null)
{
if (contentView is SkiaScrollView)
// Get page background color if set
SKColor? bgColor = null;
if (cp.BackgroundColor != null && cp.BackgroundColor != Colors.Transparent)
{
return contentView;
var color = cp.BackgroundColor;
bgColor = new SKColor(
(byte)(color.Red * 255f),
(byte)(color.Green * 255f),
(byte)(color.Blue * 255f),
(byte)(color.Alpha * 255f));
Console.WriteLine($"[CreateShellContentPage] Page BackgroundColor: {bgColor}");
}
if (contentView is SkiaScrollView scrollView)
{
if (bgColor.HasValue)
{
scrollView.BackgroundColor = bgColor.Value;
}
return scrollView;
}
else
{
var scrollView = new SkiaScrollView
var newScrollView = new SkiaScrollView
{
Content = contentView
};
return scrollView;
if (bgColor.HasValue)
{
newScrollView.BackgroundColor = bgColor.Value;
}
return newScrollView;
}
}
}

View File

@@ -1,190 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Copyright (c) 2025 MarketAlly LLC
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux;
using Microsoft.Maui.Platform.Linux.Handlers;
namespace OpenMaui.Platform.Linux.Hosting;
/// <summary>
/// Extension methods for configuring OpenMaui Linux platform in a MAUI application.
/// This enables full XAML support by registering Linux-specific handlers.
/// </summary>
public static class MauiAppBuilderExtensions
{
/// <summary>
/// Configures the application to use OpenMaui Linux platform with full XAML support.
/// </summary>
/// <param name="builder">The MAUI app builder.</param>
/// <returns>The configured MAUI app builder.</returns>
/// <example>
/// <code>
/// var builder = MauiApp.CreateBuilder();
/// builder
/// .UseMauiApp&lt;App&gt;()
/// .UseOpenMauiLinux(); // Enable Linux support with XAML
/// </code>
/// </example>
public static MauiAppBuilder UseOpenMauiLinux(this MauiAppBuilder builder)
{
builder.ConfigureMauiHandlers(handlers =>
{
// Register all Linux platform handlers
// These map MAUI virtual views to our Skia platform views
// Basic Controls
handlers.AddHandler<Button, ButtonHandler>();
handlers.AddHandler<Label, LabelHandler>();
handlers.AddHandler<Entry, EntryHandler>();
handlers.AddHandler<Editor, EditorHandler>();
handlers.AddHandler<CheckBox, CheckBoxHandler>();
handlers.AddHandler<Switch, SwitchHandler>();
handlers.AddHandler<RadioButton, RadioButtonHandler>();
// Selection Controls
handlers.AddHandler<Slider, SliderHandler>();
handlers.AddHandler<Stepper, StepperHandler>();
handlers.AddHandler<Picker, PickerHandler>();
handlers.AddHandler<DatePicker, DatePickerHandler>();
handlers.AddHandler<TimePicker, TimePickerHandler>();
// Display Controls
handlers.AddHandler<Image, ImageHandler>();
handlers.AddHandler<ImageButton, ImageButtonHandler>();
handlers.AddHandler<ActivityIndicator, ActivityIndicatorHandler>();
handlers.AddHandler<ProgressBar, ProgressBarHandler>();
// Layout Controls
handlers.AddHandler<Border, BorderHandler>();
// Collection Controls
handlers.AddHandler<CollectionView, CollectionViewHandler>();
// Navigation Controls
handlers.AddHandler<NavigationPage, NavigationPageHandler>();
handlers.AddHandler<TabbedPage, TabbedPageHandler>();
handlers.AddHandler<FlyoutPage, FlyoutPageHandler>();
handlers.AddHandler<Shell, ShellHandler>();
// Page Controls
handlers.AddHandler<Page, PageHandler>();
handlers.AddHandler<ContentPage, PageHandler>();
// Graphics
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
// Search
handlers.AddHandler<SearchBar, SearchBarHandler>();
// Web
handlers.AddHandler<WebView, WebViewHandler>();
// Window
handlers.AddHandler<Window, WindowHandler>();
});
// Register Linux-specific services
builder.Services.AddSingleton<ILinuxPlatformServices, LinuxPlatformServices>();
return builder;
}
/// <summary>
/// Configures the application to use OpenMaui Linux with custom handler configuration.
/// </summary>
/// <param name="builder">The MAUI app builder.</param>
/// <param name="configureHandlers">Action to configure additional handlers.</param>
/// <returns>The configured MAUI app builder.</returns>
public static MauiAppBuilder UseOpenMauiLinux(
this MauiAppBuilder builder,
Action<IMauiHandlersCollection>? configureHandlers)
{
builder.UseOpenMauiLinux();
if (configureHandlers != null)
{
builder.ConfigureMauiHandlers(configureHandlers);
}
return builder;
}
}
/// <summary>
/// Interface for Linux platform services.
/// </summary>
public interface ILinuxPlatformServices
{
/// <summary>
/// Gets the display server type (X11 or Wayland).
/// </summary>
DisplayServerType DisplayServer { get; }
/// <summary>
/// Gets the current DPI scale factor.
/// </summary>
float ScaleFactor { get; }
/// <summary>
/// Gets whether high contrast mode is enabled.
/// </summary>
bool IsHighContrastEnabled { get; }
}
/// <summary>
/// Display server types supported by OpenMaui.
/// </summary>
public enum DisplayServerType
{
/// <summary>X11 display server.</summary>
X11,
/// <summary>Wayland display server.</summary>
Wayland,
/// <summary>Auto-detected display server.</summary>
Auto
}
/// <summary>
/// Implementation of Linux platform services.
/// </summary>
internal class LinuxPlatformServices : ILinuxPlatformServices
{
public DisplayServerType DisplayServer => DetectDisplayServer();
public float ScaleFactor => DetectScaleFactor();
public bool IsHighContrastEnabled => DetectHighContrast();
private static DisplayServerType DetectDisplayServer()
{
var waylandDisplay = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY");
if (!string.IsNullOrEmpty(waylandDisplay))
return DisplayServerType.Wayland;
var display = Environment.GetEnvironmentVariable("DISPLAY");
if (!string.IsNullOrEmpty(display))
return DisplayServerType.X11;
return DisplayServerType.Auto;
}
private static float DetectScaleFactor()
{
// Try GDK_SCALE first
var gdkScale = Environment.GetEnvironmentVariable("GDK_SCALE");
if (float.TryParse(gdkScale, out var scale))
return scale;
// Default to 1.0
return 1.0f;
}
private static bool DetectHighContrast()
{
var highContrast = Environment.GetEnvironmentVariable("GTK_THEME");
return highContrast?.Contains("HighContrast", StringComparison.OrdinalIgnoreCase) ?? false;
}
}

View File

@@ -28,7 +28,7 @@ public static class MauiHandlerExtensions
[typeof(TimePicker)] = () => new TimePickerHandler(),
[typeof(SearchBar)] = () => new SearchBarHandler(),
[typeof(RadioButton)] = () => new RadioButtonHandler(),
[typeof(WebView)] = () => new WebViewHandler(),
[typeof(WebView)] = () => new GtkWebViewHandler(),
[typeof(Image)] = () => new ImageHandler(),
[typeof(ImageButton)] = () => new ImageButtonHandler(),
[typeof(BoxView)] = () => new BoxViewHandler(),
@@ -41,7 +41,7 @@ public static class MauiHandlerExtensions
[typeof(VerticalStackLayout)] = () => new StackLayoutHandler(),
[typeof(HorizontalStackLayout)] = () => new StackLayoutHandler(),
[typeof(AbsoluteLayout)] = () => new LayoutHandler(),
[typeof(FlexLayout)] = () => new FlexLayoutHandler(),
[typeof(FlexLayout)] = () => new LayoutHandler(),
[typeof(CollectionView)] = () => new CollectionViewHandler(),
[typeof(ListView)] = () => new CollectionViewHandler(),
[typeof(Page)] = () => new PageHandler(),

View File

@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.Maui.Platform.Linux.Hosting;
public class ScopedLinuxMauiContext : IMauiContext
{
private readonly LinuxMauiContext _parent;
public IServiceProvider Services => _parent.Services;
public IMauiHandlersFactory Handlers => _parent.Handlers;
public ScopedLinuxMauiContext(LinuxMauiContext parent)
{
_parent = parent ?? throw new ArgumentNullException(nameof(parent));
}
}