Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 20 additions & 35 deletions src/Core/src/ScreenshotDispatch.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Media;
Expand All @@ -7,50 +6,36 @@ namespace Microsoft.Maui
{
/// <summary>
/// Internal helper that routes <see cref="ViewExtensions.CaptureAsync(IView)"/>
/// and <see cref="WindowExtensions.CaptureAsync(IWindow)"/> through a keyed DI
/// hook when MAUI is built for a non-built-in platform TFM and therefore has no
/// compile-time screenshot implementation.
/// and <see cref="WindowExtensions.CaptureAsync(IWindow)"/> through the registered
/// screenshot service when MAUI is built for a non-built-in platform TFM and
/// therefore has no compile-time screenshot implementation.
/// </summary>
/// <remarks>
/// Third-party platform backends (e.g. macOS AppKit, Linux/GTK) register a
/// <see cref="Func{T, TResult}"/> of <see cref="object"/> to
/// <see cref="Task{TResult}"/> of nullable <see cref="IScreenshotResult"/>
/// (i.e. <c>Func&lt;object, Task&lt;IScreenshotResult?&gt;&gt;</c>) under one of
/// the well-known keys defined on this type. The lambda receives the handler's
/// <see cref="IElementHandler.PlatformView"/> object and returns a task whose
/// result is the screenshot (or <see langword="null"/> if capture is not
/// supported for that view). A hook that returns a <see langword="null"/>
/// task is treated as unsupported and produces a <see langword="null"/> result.
/// This contract intentionally uses only BCL types so it can ship without any
/// MAUI public API addition.
/// Third-party platform backends (e.g. macOS AppKit, Linux/GTK) register an
/// <see cref="IScreenshot"/> implementation that also implements
/// <see cref="IViewScreenshot"/> in the app's <see cref="System.IServiceProvider"/>.
/// The dispatch resolves that service from the handler's
/// <see cref="IElementHandler.MauiContext"/> and forwards the handler's platform
/// view (or, for views, its container) to
/// <see cref="IViewScreenshot.CaptureViewAsync(object)"/>. When no capable
/// service is registered (or capture is unsupported) the result is
/// <see langword="null"/>, preserving the extension methods' graceful contract.
/// </remarks>
static class ScreenshotDispatch
{
/// <summary>
/// DI service key for the <see cref="IView"/> screenshot hook.
/// </summary>
public const string ViewCaptureKey = "Microsoft.Maui.ViewCapture";

/// <summary>
/// DI service key for the <see cref="IWindow"/> screenshot hook.
/// </summary>
public const string WindowCaptureKey = "Microsoft.Maui.WindowCapture";

public static Task<IScreenshotResult?> CaptureAsync(IElementHandler? handler, string serviceKey)
public static Task<IScreenshotResult?> CaptureAsync(IElementHandler? handler, object? captureView)
{
var platformView = handler?.PlatformView;
if (platformView is null)
if (captureView is null)
return Task.FromResult<IScreenshotResult?>(null);

if (handler!.MauiContext?.Services is not IKeyedServiceProvider keyedProvider)
return Task.FromResult<IScreenshotResult?>(null);

var capture = keyedProvider.GetKeyedService<Func<object, Task<IScreenshotResult?>>>(serviceKey);

if (capture is null)
if (handler?.MauiContext?.Services?.GetService(typeof(IScreenshot)) is not IScreenshot screenshot
|| !screenshot.IsCaptureSupported
|| screenshot is not IViewScreenshot viewScreenshot)
{
return Task.FromResult<IScreenshotResult?>(null);
}

return capture(platformView) ?? Task.FromResult<IScreenshotResult?>(null);
return viewScreenshot.CaptureViewAsync(captureView) ?? Task.FromResult<IScreenshotResult?>(null);
}
}
}
30 changes: 18 additions & 12 deletions src/Core/src/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ void BuildFlatList(IView view, List<IView> flatList)
/// <remarks>
/// On non-built-in platform TFMs (e.g. <c>net10.0-macos</c> AppKit backends,
/// <c>net10.0</c> Linux/GTK backends) where MAUI does not ship a screenshot
/// implementation, capture is routed through a keyed DI hook. Third-party
/// platform backends can opt in by registering a
/// <see cref="Func{T, TResult}"/> of <see cref="object"/> to
/// <c>Task&lt;IScreenshotResult?&gt;</c> under the service key
/// <c>"Microsoft.Maui.ViewCapture"</c>:
/// implementation, capture is routed through the registered screenshot service.
/// Third-party platform backends opt in by registering an <see cref="IScreenshot"/>
/// implementation that also implements <see cref="IViewScreenshot"/> in the app's
/// <see cref="System.IServiceProvider"/>:
/// <code>
/// builder.Services.AddKeyedSingleton&lt;Func&lt;object, Task&lt;IScreenshotResult?&gt;&gt;&gt;(
/// "Microsoft.Maui.ViewCapture",
/// (_, _) =&gt; platformView =&gt; ((AppKit.NSView)platformView).CaptureAsync());
/// builder.Services.AddSingleton&lt;IScreenshot, AppKitScreenshotImplementation&gt;();
/// </code>
/// If no hook is registered (or the <see cref="IElementHandler.PlatformView"/>
/// is <see langword="null"/>), the returned task resolves to
/// <see langword="null"/>.
/// The dispatch resolves that service from the handler's
/// <see cref="IElementHandler.MauiContext"/> and forwards the view's container view
/// (or, failing that, its platform view) to
/// <see cref="IViewScreenshot.CaptureViewAsync(object)"/>. When no capable service is
/// registered (or the <see cref="IElementHandler.PlatformView"/> is
/// <see langword="null"/>), the returned task resolves to <see langword="null"/>.
/// </remarks>
public static Task<IScreenshotResult?> CaptureAsync(this IView view)
{
Expand All @@ -97,7 +97,13 @@ void BuildFlatList(IView view, List<IView> flatList)

return CaptureAsync(platformView);
#else
return ScreenshotDispatch.CaptureAsync(view?.Handler, ScreenshotDispatch.ViewCaptureKey);
// Prefer the container view (clip/shadow/border) like the #if PLATFORM path's
// view.ToPlatform() does; fall back to the raw platform view. The shared dispatch
// helper resolves the registered IViewScreenshot and stays graceful (returns null
// when capture is unavailable) to preserve this path's contract.
var handler = view?.Handler;
var captureView = (handler as IViewHandler)?.ContainerView ?? handler?.PlatformView;
return ScreenshotDispatch.CaptureAsync(handler, captureView);
Comment thread
kubaflo marked this conversation as resolved.
#endif
}

Expand Down
24 changes: 12 additions & 12 deletions src/Core/src/WindowExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,18 @@ public static partial class WindowExtensions
/// <remarks>
/// On non-built-in platform TFMs (e.g. <c>net10.0-macos</c> AppKit backends,
/// <c>net10.0</c> Linux/GTK backends) where MAUI does not ship a screenshot
/// implementation, capture is routed through a keyed DI hook. Third-party
/// platform backends can opt in by registering a
/// <see cref="Func{T, TResult}"/> of <see cref="object"/> to
/// <c>Task&lt;IScreenshotResult?&gt;</c> under the service key
/// <c>"Microsoft.Maui.WindowCapture"</c>:
/// implementation, capture is routed through the registered screenshot service.
/// Third-party platform backends opt in by registering an <see cref="IScreenshot"/>
/// implementation that also implements <see cref="IViewScreenshot"/> in the app's
/// <see cref="System.IServiceProvider"/>:
/// <code>
/// builder.Services.AddKeyedSingleton&lt;Func&lt;object, Task&lt;IScreenshotResult?&gt;&gt;&gt;(
/// "Microsoft.Maui.WindowCapture",
/// (_, _) =&gt; platformWindow =&gt; ((AppKit.NSWindow)platformWindow).CaptureAsync());
/// builder.Services.AddSingleton&lt;IScreenshot, AppKitScreenshotImplementation&gt;();
/// </code>
/// If no hook is registered (or the <see cref="IElementHandler.PlatformView"/>
/// is <see langword="null"/>), the returned task resolves to
/// <see langword="null"/>.
/// The dispatch resolves that service from the handler's
/// <see cref="IElementHandler.MauiContext"/> and forwards the window's platform view
/// to <see cref="IViewScreenshot.CaptureViewAsync(object)"/>. When no capable service
/// is registered (or the <see cref="IElementHandler.PlatformView"/> is
/// <see langword="null"/>), the returned task resolves to <see langword="null"/>.
/// </remarks>
public static Task<IScreenshotResult?> CaptureAsync(this IWindow window)
{
Expand All @@ -47,7 +46,8 @@ public static partial class WindowExtensions

return CaptureAsync(platformView);
#else
return ScreenshotDispatch.CaptureAsync(window?.Handler, ScreenshotDispatch.WindowCaptureKey);
var handler = window?.Handler;
return ScreenshotDispatch.CaptureAsync(handler, handler?.PlatformView);
Comment thread
kubaflo marked this conversation as resolved.
#endif
}

Expand Down
233 changes: 0 additions & 233 deletions src/Core/tests/UnitTests/Extensions/ScreenshotDispatchTests.cs

This file was deleted.

Loading
Loading