├── Assets
├── plugin.maui.screensecurity_128x128.png
└── plugin.maui.screensecurity_template.jpg
├── Plugin.Maui.ScreenSecurity
├── Platforms
│ ├── iOS
│ │ ├── ThemeStyle.cs
│ │ ├── Helpers
│ │ │ ├── StringExtensions.cs
│ │ │ ├── IOSHelpers.cs
│ │ │ └── UIColorExtensions.cs
│ │ ├── Protections
│ │ │ ├── ScreenRecordingProtectionManager.cs
│ │ │ ├── ImageProtectionManager.cs
│ │ │ ├── ColorProtectionManager.cs
│ │ │ ├── ScreenshotProtectionManager.cs
│ │ │ └── BlurProtectionManager.cs
│ │ └── ScreenSecurity.ios.cs
│ ├── Android
│ │ ├── Helpers
│ │ │ └── AndroidHelpers.cs
│ │ └── ScreenSecurity.android.cs
│ └── Windows
│ │ ├── NativeMethods.cs
│ │ └── ScreenSecurity.windows.cs
├── Handlers
│ ├── ScreenCaptureEventHandler.cs
│ └── ErrorsHandler.cs
├── ScreenSecurity.cs
├── ScreenSecurity.net.cs
├── ScreenProtectionOptions.cs
├── MauiAppBuilderExtensions.cs
├── IScreenSecurity.cs
└── Plugin.Maui.ScreenSecurity.csproj
├── samples
├── ScreenSecurityBlazorSample
│ ├── wwwroot
│ │ ├── favicon.png
│ │ ├── index.html
│ │ └── css
│ │ │ └── app.css
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Resources
│ │ ├── Fonts
│ │ │ └── OpenSans-Regular.ttf
│ │ ├── AppIcon
│ │ │ ├── appicon.svg
│ │ │ └── appiconfg.svg
│ │ ├── Raw
│ │ │ └── AboutAssets.txt
│ │ ├── Splash
│ │ │ └── splash.svg
│ │ └── Images
│ │ │ └── dotnet_bot.svg
│ ├── Platforms
│ │ ├── Android
│ │ │ ├── Resources
│ │ │ │ └── values
│ │ │ │ │ └── colors.xml
│ │ │ ├── MainApplication.cs
│ │ │ ├── MainActivity.cs
│ │ │ └── AndroidManifest.xml
│ │ ├── iOS
│ │ │ ├── AppDelegate.cs
│ │ │ ├── Program.cs
│ │ │ └── Info.plist
│ │ └── Windows
│ │ │ ├── App.xaml
│ │ │ ├── app.manifest
│ │ │ ├── App.xaml.cs
│ │ │ └── Package.appxmanifest
│ ├── Components
│ │ ├── Routes.razor
│ │ ├── Layout
│ │ │ ├── MainLayout.razor
│ │ │ ├── NavMenu.razor
│ │ │ ├── MainLayout.razor.css
│ │ │ └── NavMenu.razor.css
│ │ ├── _Imports.razor
│ │ └── Pages
│ │ │ ├── ProtectedPage.razor
│ │ │ ├── UnprotectedPage.razor
│ │ │ └── Home.razor
│ ├── App.xaml.cs
│ ├── MainPage.xaml.cs
│ ├── MainPage.xaml
│ ├── MauiProgram.cs
│ ├── App.xaml
│ └── ScreenSecurityBlazorSample.csproj
└── ScreenSecuritySample
│ ├── Resources
│ ├── Images
│ │ ├── protection_bg.png
│ │ └── dotnet_bot.svg
│ ├── Fonts
│ │ ├── OpenSans-Regular.ttf
│ │ └── OpenSans-Semibold.ttf
│ ├── AppIcon
│ │ ├── appicon.svg
│ │ └── appiconfg.svg
│ ├── Raw
│ │ └── AboutAssets.txt
│ ├── Splash
│ │ └── splash.svg
│ └── Styles
│ │ ├── Colors.xaml
│ │ └── Styles.xaml
│ ├── Properties
│ └── launchSettings.json
│ ├── Platforms
│ ├── Android
│ │ ├── Resources
│ │ │ └── values
│ │ │ │ └── colors.xml
│ │ ├── MainApplication.cs
│ │ ├── MainActivity.cs
│ │ └── AndroidManifest.xml
│ ├── iOS
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ └── Info.plist
│ └── Windows
│ │ ├── App.xaml
│ │ ├── App.xaml.cs
│ │ ├── app.manifest
│ │ └── Package.appxmanifest
│ ├── App.xaml.cs
│ ├── AppShell.xaml.cs
│ ├── AppShell.xaml
│ ├── App.xaml
│ ├── MauiProgram.cs
│ ├── Views
│ ├── SecondPage.xaml
│ ├── SecondPage.xaml.cs
│ ├── MainPage.xaml
│ └── MainPage.xaml.cs
│ └── ScreenSecuritySample.csproj
├── LICENSE
├── Plugin.Maui.ScreenSecurity.sln
├── CHANGELOG.md
├── README.md
└── .gitignore
/Assets/plugin.maui.screensecurity_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/Assets/plugin.maui.screensecurity_128x128.png
--------------------------------------------------------------------------------
/Assets/plugin.maui.screensecurity_template.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/Assets/plugin.maui.screensecurity_template.jpg
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/ThemeStyle.cs:
--------------------------------------------------------------------------------
1 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
2 |
3 | internal enum ThemeStyle
4 | {
5 | Light,
6 | Dark
7 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/wwwroot/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/samples/ScreenSecurityBlazorSample/wwwroot/favicon.png
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Images/protection_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/samples/ScreenSecuritySample/Resources/Images/protection_bg.png
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Windows Machine": {
4 | "commandName": "MsixPackage",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/samples/ScreenSecuritySample/Resources/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Fonts/OpenSans-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/samples/ScreenSecuritySample/Resources/Fonts/OpenSans-Semibold.ttf
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Windows Machine": {
4 | "commandName": "MsixPackage",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Resources/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FabriBertani/Plugin.Maui.ScreenSecurity/HEAD/samples/ScreenSecurityBlazorSample/Resources/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/AppIcon/appicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #512BD4
4 | #2B0B98
5 | #2B0B98
6 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #512BD4
4 | #2B0B98
5 | #2B0B98
6 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Resources/AppIcon/appicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace ScreenSecuritySample;
4 |
5 | [Register(nameof(AppDelegate))]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace ScreenSecurityBlazorSample;
4 |
5 | [Register(nameof(AppDelegate))]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Routes.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Layout/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
4 |
7 |
8 |
9 |
10 | @Body
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace ScreenSecuritySample;
2 |
3 | public partial class App : Application
4 | {
5 | public App()
6 | {
7 | InitializeComponent();
8 | }
9 |
10 | protected override Window CreateWindow(IActivationState activationState)
11 | {
12 | return new Window(new AppShell());
13 | }
14 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Handlers/ScreenCaptureEventHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Plugin.Maui.ScreenSecurity.Handlers;
2 |
3 | internal static class ScreenCaptureEventHandler
4 | {
5 | internal static event EventHandler? ScreenCaptured;
6 |
7 | internal static void RaiseScreenCaptured()
8 | {
9 | ScreenCaptured?.Invoke(null, EventArgs.Empty);
10 | }
11 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Android/MainApplication.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Runtime;
3 |
4 | namespace ScreenSecuritySample;
5 |
6 | [Application]
7 | public class MainApplication(IntPtr handle, JniHandleOwnership ownership) : MauiApplication(handle, ownership)
8 | {
9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
10 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Windows/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Windows/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace ScreenSecurityBlazorSample
2 | {
3 | public partial class App : Application
4 | {
5 | public App()
6 | {
7 | InitializeComponent();
8 | }
9 |
10 | protected override Window CreateWindow(IActivationState? activationState)
11 | {
12 | return new Window(new MainPage());
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Android/MainApplication.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Runtime;
3 |
4 | namespace ScreenSecurityBlazorSample;
5 |
6 | [Application]
7 | public class MainApplication(IntPtr handle,
8 | JniHandleOwnership ownership) : MauiApplication(handle, ownership)
9 | {
10 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
11 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using System.Net.Http.Json
3 | @using Microsoft.AspNetCore.Components.Forms
4 | @using Microsoft.AspNetCore.Components.Routing
5 | @using Microsoft.AspNetCore.Components.Web
6 | @using Microsoft.AspNetCore.Components.Web.Virtualization
7 | @using Microsoft.JSInterop
8 | @using ScreenSecurityBlazorSample
9 | @using ScreenSecurityBlazorSample.Components
10 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/AppShell.xaml.cs:
--------------------------------------------------------------------------------
1 | using ScreenSecuritySample.Views;
2 |
3 | namespace ScreenSecuritySample;
4 |
5 | public partial class AppShell : Shell
6 | {
7 | public AppShell()
8 | {
9 | InitializeComponent();
10 |
11 | RegisterRoutes();
12 | }
13 |
14 | private static void RegisterRoutes()
15 | {
16 | Routing.RegisterRoute("unprotected_page", typeof(SecondPage));
17 | }
18 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Pages/ProtectedPage.razor:
--------------------------------------------------------------------------------
1 | @page "/protected_page"
2 |
3 | @using Plugin.Maui.ScreenSecurity;
4 |
5 | Protected page
6 |
7 | This page is protected against content exposure.
8 |
9 | @code {
10 | protected override void OnInitialized()
11 | {
12 | ScreenSecurity.Default?.ActivateScreenSecurityProtection();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Pages/UnprotectedPage.razor:
--------------------------------------------------------------------------------
1 | @page "/unprotected_page"
2 |
3 | @using Plugin.Maui.ScreenSecurity;
4 |
5 | Unprotected Page
6 |
7 | This page is NOT protected against content exposure.
8 |
9 | @code {
10 | protected override void OnInitialized()
11 | {
12 | ScreenSecurity.Default?.DeactivateScreenSecurityProtection();
13 | }
14 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/iOS/Program.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace ScreenSecuritySample;
4 |
5 | public class Program
6 | {
7 | // This is the main entry point of the application.
8 | static void Main(string[] args)
9 | {
10 | // if you want to use a different Application Delegate class from "AppDelegate"
11 | // you can specify it here.
12 | UIApplication.Main(args, null, typeof(AppDelegate));
13 | }
14 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/iOS/Program.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace ScreenSecurityBlazorSample;
4 |
5 | public class Program
6 | {
7 | // This is the main entry point of the application.
8 | static void Main(string[] args)
9 | {
10 | // if you want to use a different Application Delegate class from "AppDelegate"
11 | // you can specify it here.
12 | UIApplication.Main(args, null, typeof(AppDelegate));
13 | }
14 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 |
4 | namespace ScreenSecurityBlazorSample;
5 |
6 | [Activity(
7 | Theme = "@style/Maui.SplashTheme",
8 | MainLauncher = true,
9 | Exported = true,
10 | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
11 | public class MainActivity : MauiAppCompatActivity
12 | {
13 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/ScreenSecurity.cs:
--------------------------------------------------------------------------------
1 | namespace Plugin.Maui.ScreenSecurity;
2 |
3 | public static class ScreenSecurity
4 | {
5 | ///
6 | /// Provides the default implementation for static usage of this API.
7 | ///
8 | private static IScreenSecurity? _defaultImplementation;
9 |
10 | public static IScreenSecurity Default => _defaultImplementation ??= new ScreenSecurityImplementation();
11 |
12 | internal static void SetDefault(IScreenSecurity? implementation) => _defaultImplementation = implementation;
13 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/AppShell.xaml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using System.Diagnostics;
4 |
5 | namespace ScreenSecuritySample;
6 |
7 | [Activity(
8 | Theme = "@style/Maui.SplashTheme",
9 | MainLauncher = true,
10 | Exported = true,
11 | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
12 | public class MainActivity : MauiAppCompatActivity
13 | {
14 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity;
2 |
3 | namespace ScreenSecurityBlazorSample
4 | {
5 | public partial class MainPage : ContentPage
6 | {
7 | public MainPage()
8 | {
9 | InitializeComponent();
10 | }
11 |
12 | // Uncomment this to enable screen protection on the entire app.
13 | // Note: If you open the Unprotected Page, protection will be disabled.
14 | //protected override void OnAppearing()
15 | //{
16 | // base.OnAppearing();
17 |
18 | // ScreenSecurity.Default?.ActivateScreenSecurityProtection();
19 | //}
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Helpers/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
4 |
5 | internal static partial class StringExtensions
6 | {
7 | internal static bool IsHexColor(this string hexColor) => ValidHexColorRegex().IsMatch(hexColor);
8 |
9 | internal static bool IsValidImage(this string image) => ValidImageRegex().IsMatch(image);
10 |
11 | [GeneratedRegex("([^\\s]+(\\.(?i)(jpe?g|png|gif|bmp|svg))$)", RegexOptions.None)]
12 | private static partial Regex ValidImageRegex();
13 |
14 | [GeneratedRegex("^#([a-fA-F0-9]{8}|[a-fA-F0-9]{6})$")]
15 | private static partial Regex ValidHexColorRegex();
16 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Raw/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories). Deployment of the asset to your application
3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
4 |
5 |
6 |
7 | These files will be deployed with you package and will be accessible using Essentials:
8 |
9 | async Task LoadMauiAsset()
10 | {
11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
12 | using var reader = new StreamReader(stream);
13 |
14 | var contents = reader.ReadToEnd();
15 | }
16 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Resources/Raw/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories). Deployment of the asset to your application
3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
4 |
5 |
6 |
7 | These files will be deployed with your package and will be accessible using Essentials:
8 |
9 | async Task LoadMauiAsset()
10 | {
11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
12 | using var reader = new StreamReader(stream);
13 |
14 | var contents = reader.ReadToEnd();
15 | }
16 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Windows/App.xaml.cs:
--------------------------------------------------------------------------------
1 | // To learn more about WinUI, the WinUI project structure,
2 | // and more about our project templates, see: http://aka.ms/winui-project-info.
3 |
4 | namespace ScreenSecuritySample.WinUI;
5 |
6 | ///
7 | /// Provides application-specific behavior to supplement the default Application class.
8 | ///
9 | public partial class App : MauiWinUIApplication
10 | {
11 | ///
12 | /// Initializes the singleton application object. This is the first line of authored code
13 | /// executed, and as such is the logical equivalent of main() or WinMain().
14 | ///
15 | public App()
16 | {
17 | this.InitializeComponent();
18 | }
19 |
20 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
21 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/MauiProgram.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity;
2 | using ScreenSecuritySample.Views;
3 |
4 | namespace ScreenSecuritySample;
5 |
6 | public static class MauiProgram
7 | {
8 | public static MauiApp CreateMauiApp()
9 | {
10 | var builder = MauiApp.CreateBuilder();
11 | builder
12 | .UseMauiApp()
13 | .ConfigureFonts(fonts =>
14 | {
15 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
16 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
17 | })
18 | .UseScreenSecurity();
19 |
20 | // Register Main Page
21 | builder.Services.AddScoped();
22 | builder.Services.AddScoped();
23 |
24 | return builder.Build();
25 | }
26 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | true/PM
12 | PerMonitorV2, PerMonitor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | true/PM
12 | PerMonitorV2, PerMonitor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/MauiProgram.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Plugin.Maui.ScreenSecurity;
3 |
4 | namespace ScreenSecurityBlazorSample
5 | {
6 | public static class MauiProgram
7 | {
8 | public static MauiApp CreateMauiApp()
9 | {
10 | var builder = MauiApp.CreateBuilder();
11 | builder
12 | .UseMauiApp()
13 | .ConfigureFonts(fonts =>
14 | {
15 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
16 | })
17 | .UseScreenSecurity();
18 |
19 | builder.Services.AddMauiBlazorWebView();
20 |
21 | #if DEBUG
22 | builder.Services.AddBlazorWebViewDeveloperTools();
23 | builder.Logging.AddDebug();
24 | #endif
25 |
26 | return builder.Build();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/Android/Helpers/AndroidHelpers.cs:
--------------------------------------------------------------------------------
1 | using Android.OS;
2 | using Java.Lang;
3 | using Java.Util.Concurrent;
4 | using Plugin.Maui.ScreenSecurity.Handlers;
5 | using static Android.App.Activity;
6 | using Object = Java.Lang.Object;
7 |
8 | namespace Plugin.Maui.ScreenSecurity.Platforms.Android;
9 |
10 | internal class CustomScreenCaptureCallback : Object, IScreenCaptureCallback
11 | {
12 | public void OnScreenCaptured()
13 | {
14 | ScreenCaptureEventHandler.RaiseScreenCaptured();
15 | }
16 | }
17 |
18 | internal class MainThreadExecutor : Object, IExecutor
19 | {
20 | private readonly Handler _handler = new(Looper.MainLooper ?? throw new InvalidOperationException("MainLooper is null"));
21 |
22 | public void Execute(IRunnable? command)
23 | {
24 | if (command is not null)
25 | _handler.Post(command);
26 | }
27 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Handlers/ErrorsHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.ExceptionServices;
2 |
3 | namespace Plugin.Maui.ScreenSecurity.Handlers;
4 |
5 | internal static class ErrorsHandler
6 | {
7 | internal static void HandleException(string methodName, bool throwErrors, Exception ex)
8 | {
9 | System.Diagnostics.Trace.WriteLine($"{methodName} failed with Exception message: {ex.Message}");
10 | System.Diagnostics.Trace.WriteLine($"Exception Stacktrace: {ex.StackTrace}");
11 | if (ex.InnerException is not null)
12 | System.Diagnostics.Trace.WriteLine($"With InnerException: {ex.InnerException}");
13 |
14 | if (throwErrors)
15 | {
16 | ExceptionDispatchInfo.Capture(ex).Throw();
17 |
18 | // Keep an explicit return so the compiler knows this code path doesn't continue.
19 | return;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Windows/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 |
3 | // To learn more about WinUI, the WinUI project structure,
4 | // and more about our project templates, see: http://aka.ms/winui-project-info.
5 |
6 | namespace ScreenSecurityBlazorSample.WinUI
7 | {
8 | ///
9 | /// Provides application-specific behavior to supplement the default Application class.
10 | ///
11 | public partial class App : MauiWinUIApplication
12 | {
13 | ///
14 | /// Initializes the singleton application object. This is the first line of authored code
15 | /// executed, and as such is the logical equivalent of main() or WinMain().
16 | ///
17 | public App()
18 | {
19 | this.InitializeComponent();
20 | }
21 |
22 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/ScreenSecurity.net.cs:
--------------------------------------------------------------------------------
1 | namespace Plugin.Maui.ScreenSecurity;
2 |
3 | internal partial class ScreenSecurityImplementation : IScreenSecurity
4 | {
5 | public void ActivateScreenSecurityProtection()
6 | {
7 | throw new NotImplementedException();
8 | }
9 |
10 | public void ActivateScreenSecurityProtection(bool blurScreenProtection, bool preventScreenshot, bool preventScreenRecording)
11 | {
12 | throw new NotImplementedException();
13 | }
14 |
15 | public void ActivateScreenSecurityProtection(ScreenProtectionOptions screenProtectionOptions)
16 | {
17 | throw new NotImplementedException();
18 | }
19 |
20 | public void DeactivateScreenSecurityProtection()
21 | {
22 | throw new NotImplementedException();
23 | }
24 |
25 | public bool IsProtectionEnabled { get; private set; }
26 |
27 | public bool ThrowErrors { get; set; }
28 |
29 | public event EventHandler? ScreenCaptured;
30 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ScreenSecurityBlazorSample
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Loading...
19 |
20 |
21 | An unhandled error has occurred.
22 |
Reload
23 |
🗙
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Fabricio Bertani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSRequiresIPhoneOS
6 |
7 | UIDeviceFamily
8 |
9 | 1
10 | 2
11 |
12 | UIRequiredDeviceCapabilities
13 |
14 | arm64
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | XSAppIconAssets
30 | Assets.xcassets/appicon.appiconset
31 | MinimumOSVersion
32 | 15.0
33 |
34 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 | #512bdf
10 | White
11 |
12 |
16 |
17 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Layout/NavMenu.razor:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
28 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Views/SecondPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
18 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSRequiresIPhoneOS
6 |
7 | UIDeviceFamily
8 |
9 | 1
10 | 2
11 |
12 | UIRequiredDeviceCapabilities
13 |
14 | arm64
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | XSAppIconAssets
30 | Assets.xcassets/appicon.appiconset
31 | MinimumOSVersion
32 | 15.0
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/ScreenProtectionOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Plugin.Maui.ScreenSecurity;
2 |
3 | public class ScreenProtectionOptions
4 | {
5 | private string _hexColor = string.Empty;
6 |
7 | private string _image = string.Empty;
8 |
9 | ///
10 | /// Hexadecimal color as string in the form of
11 | /// #RGB , #RGBA , #RRGGBB or #RRGGBBAA .
12 | /// This cannot be set if the Image property is already set.
13 | ///
14 | public string HexColor
15 | {
16 | get => _hexColor;
17 | set
18 | {
19 | if (string.IsNullOrEmpty(Image) && !string.IsNullOrEmpty(value))
20 | _hexColor = value;
21 | }
22 | }
23 |
24 | ///
25 | /// Name with extension of the image to use.
26 | /// This cannot be set if the HexColor property is already set.
27 | ///
28 | public string Image
29 | {
30 | get => _image;
31 | set
32 | {
33 | if (string.IsNullOrEmpty(HexColor) && !string.IsNullOrEmpty(value))
34 | _image = value;
35 | }
36 | }
37 |
38 | public bool PreventScreenshot { get; set; } = true;
39 |
40 | public bool PreventScreenRecording { get; set; } = true;
41 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Pages/Home.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 |
3 | @using Plugin.Maui.ScreenSecurity;
4 |
5 | Home
6 |
7 | Use the buttons below to activate or deactivate screen protection.
8 |
9 |
10 |
11 | Enable Screen Security
12 |
13 |
14 | Disable Screen Security
15 |
16 |
17 |
18 |
19 | If you want to activate screen protection for the whole application, uncomment the OnAppearing section of MainPage.xaml.cs
20 |
21 | @code {
22 | async Task EnableScreenSecurity()
23 | {
24 | ScreenSecurity.Default?.ActivateScreenSecurityProtection();
25 |
26 | if (Application.Current?.Windows[0].Page is not null)
27 | await Application.Current.Windows[0].Page.DisplayAlert("Screen Security", "Screen security enabled.", "OK");
28 | }
29 |
30 | async Task DisableScreenSecurity()
31 | {
32 | ScreenSecurity.Default?.DeactivateScreenSecurityProtection();
33 |
34 | if (Application.Current?.Windows[0].Page is not null)
35 | await Application.Current.Windows[0].Page.DisplayAlert("Screen Security", "Screen security disabled.", "OK");
36 | }
37 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Views/SecondPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity;
2 |
3 | namespace ScreenSecuritySample.Views;
4 |
5 | public partial class SecondPage : ContentPage
6 | {
7 | private readonly IScreenSecurity _screenSecurity;
8 |
9 | public SecondPage(IScreenSecurity screenSecurity)
10 | {
11 | InitializeComponent();
12 |
13 | _screenSecurity = screenSecurity;
14 | }
15 |
16 | protected override void OnAppearing()
17 | {
18 | base.OnAppearing();
19 |
20 | // Deactivate the screen security protection.
21 | _screenSecurity.DeactivateScreenSecurityProtection();
22 |
23 | isEnabledLabel.Text = $"Screen protection enabled: {_screenSecurity.IsProtectionEnabled}";
24 |
25 | _screenSecurity.ScreenCaptured += OnScreenCaptured;
26 | }
27 |
28 | protected override void OnDisappearing()
29 | {
30 | _screenSecurity.ScreenCaptured -= OnScreenCaptured;
31 |
32 | base.OnDisappearing();
33 | }
34 |
35 | private async void Button_Clicked(object sender, EventArgs e)
36 | {
37 | await Shell.Current.GoToAsync("..", true);
38 | }
39 |
40 | private async void OnScreenCaptured(object sender, EventArgs e)
41 | {
42 | string title = "ScreenSecuritySample";
43 | string message = "Screen was captured by screenshot or recording.";
44 |
45 | await Shell.Current.DisplayAlert(title, message, "Ok");
46 | }
47 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Views/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
18 |
23 |
28 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Splash/splash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/AppIcon/appiconfg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Resources/AppIcon/appiconfg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Resources/Splash/splash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Platforms/Windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 | $placeholder$
13 | User Name
14 | $placeholder$.png
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Layout/MainLayout.razor.css:
--------------------------------------------------------------------------------
1 | .page {
2 | position: relative;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | main {
8 | flex: 1;
9 | }
10 |
11 | .sidebar {
12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
13 | }
14 |
15 | .top-row {
16 | background-color: #f7f7f7;
17 | border-bottom: 1px solid #d6d5d5;
18 | justify-content: flex-end;
19 | height: 3.5rem;
20 | display: flex;
21 | align-items: center;
22 | }
23 |
24 | .top-row ::deep a, .top-row ::deep .btn-link {
25 | white-space: nowrap;
26 | margin-left: 1.5rem;
27 | text-decoration: none;
28 | }
29 |
30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | .top-row ::deep a:first-child {
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 |
39 | @media (max-width: 640.98px) {
40 | .top-row {
41 | justify-content: space-between;
42 | }
43 |
44 | .top-row ::deep a, .top-row ::deep .btn-link {
45 | margin-left: 0;
46 | }
47 | }
48 |
49 | @media (min-width: 641px) {
50 | .page {
51 | flex-direction: row;
52 | }
53 |
54 | .sidebar {
55 | width: 250px;
56 | height: 100vh;
57 | position: sticky;
58 | top: 0;
59 | }
60 |
61 | .top-row {
62 | position: sticky;
63 | top: 0;
64 | z-index: 1;
65 | }
66 |
67 | .top-row.auth ::deep a:first-child {
68 | flex: 1;
69 | text-align: right;
70 | width: 0;
71 | }
72 |
73 | .top-row, article {
74 | padding-left: 2rem !important;
75 | padding-right: 1.5rem !important;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Helpers/IOSHelpers.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
4 |
5 | internal static class IOSHelpers
6 | {
7 | internal static UIWindow? GetWindow()
8 | {
9 | if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
10 | {
11 | using var scenes = UIApplication.SharedApplication.ConnectedScenes;
12 | var windowScene = scenes
13 | .ToArray()
14 | .MinBy(scene => (int)scene.Session.Role);
15 |
16 | var window = windowScene?.Windows.FirstOrDefault();
17 |
18 | return window;
19 | }
20 | else if (UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
21 | {
22 | var windowScene = UIApplication.SharedApplication.ConnectedScenes
23 | .OfType()
24 | .LastOrDefault();
25 |
26 | var window = windowScene?.KeyWindow
27 | ?? windowScene?.Windows.FirstOrDefault(w => w.IsKeyWindow)
28 | ?? windowScene?.Windows.FirstOrDefault();
29 |
30 | return window;
31 | }
32 | else if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
33 | {
34 | return UIApplication.SharedApplication.ConnectedScenes
35 | .OfType()
36 | .SelectMany(s => s.Windows)
37 | .FirstOrDefault(w => w.IsKeyWindow);
38 | }
39 | else if (UIDevice.CurrentDevice.CheckSystemVersion(14, 2))
40 | return UIApplication.SharedApplication.Windows.FirstOrDefault(o => o.IsKeyWindow);
41 | else
42 | return UIApplication.SharedApplication.KeyWindow;
43 | }
44 |
45 | internal static ThemeStyle GetCurrentTheme()
46 | {
47 | return Application.Current?.RequestedTheme == AppTheme.Dark
48 | ? ThemeStyle.Dark : ThemeStyle.Light;
49 | }
50 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Protections/ScreenRecordingProtectionManager.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity.Handlers;
2 | using UIKit;
3 |
4 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
5 |
6 | internal class ScreenRecordingProtectionManager
7 | {
8 | internal static void HandleScreenRecordingProtection(bool enabled, bool throwErrors, string withColor = "", UIWindow? window = null)
9 | {
10 | UIScreen.Notifications.ObserveCapturedDidChange((sender, args) =>
11 | {
12 | try
13 | {
14 | if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
15 | {
16 | if (UITraitCollection.CurrentTraitCollection.SceneCaptureState != UISceneCaptureState.Active)
17 | return;
18 |
19 | if (enabled)
20 | EnableScreenRecordingProtection(throwErrors, withColor, window);
21 | else
22 | DisableScreenRecordingProtection(window, throwErrors);
23 | }
24 | else
25 | DisableScreenRecordingProtection(window, throwErrors);
26 | }
27 | catch (Exception ex)
28 | {
29 | ErrorsHandler.HandleException(nameof(HandleScreenRecordingProtection), throwErrors, ex);
30 | }
31 | });
32 | }
33 |
34 | private static void EnableScreenRecordingProtection(bool throwErrors, string withColor = "", UIWindow? window = null)
35 | {
36 | if (!string.IsNullOrEmpty(withColor))
37 | ColorProtectionManager.EnableColor(window, withColor, throwErrors);
38 | else
39 | BlurProtectionManager.EnableBlur(window, ThemeStyle.Light, throwErrors);
40 | }
41 |
42 | private static void DisableScreenRecordingProtection(UIWindow? window, bool throwErrors)
43 | {
44 | BlurProtectionManager.DisableBlur(window, throwErrors);
45 |
46 | ColorProtectionManager.DisableColor(throwErrors);
47 | }
48 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Platforms/Windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | $placeholder$
15 | User Name
16 | $placeholder$.png
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Helpers/UIColorExtensions.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
4 |
5 | internal static class UIColorExtensions
6 | {
7 | internal static UIColor FromHex(this UIColor _, string hexValue)
8 | {
9 | var colorString = hexValue.TrimStart('#');
10 |
11 | float red, green, blue, alpha;
12 |
13 | switch (colorString.Length)
14 | {
15 | // #RGB
16 | case 3:
17 | red = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(0, 1)), 16) / 255f;
18 | green = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(1, 1)), 16) / 255f;
19 | blue = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(2, 1)), 16) / 255f;
20 |
21 | return UIColor.FromRGB(red, green, blue);
22 |
23 | // #RGBA
24 | case 4:
25 | red = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(0, 1)), 16) / 255f;
26 | green = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(1, 1)), 16) / 255f;
27 | blue = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(2, 1)), 16) / 255f;
28 | alpha = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(3, 1)), 16) / 255f;
29 |
30 | return UIColor.FromRGBA(red, green, blue, alpha);
31 |
32 | // #RRGGBB
33 | case 6:
34 | red = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
35 | green = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
36 | blue = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
37 |
38 | return UIColor.FromRGB(red, green, blue);
39 |
40 | // #RRGGBBAA
41 | case 8:
42 | red = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
43 | green = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
44 | blue = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
45 | alpha = Convert.ToInt32(colorString.Substring(6, 2), 16) / 255f;
46 |
47 | return UIColor.FromRGBA(red, green, blue, alpha);
48 |
49 | default:
50 | throw new ArgumentOutOfRangeException(nameof(hexValue));
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Styles/Colors.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | #512BD4
8 | #DFD8F7
9 | #2B0B98
10 | White
11 | Black
12 | #E1E1E1
13 | #C8C8C8
14 | #ACACAC
15 | #919191
16 | #6E6E6E
17 | #404040
18 | #212121
19 | #141414
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | #F7B548
35 | #FFD590
36 | #FFE5B9
37 | #28C2D1
38 | #7BDDEF
39 | #C3F2F4
40 | #3E8EED
41 | #72ACF1
42 | #A7CBF6
43 |
44 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/MauiAppBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #if ANDROID
2 | using Android.OS;
3 | using Plugin.Maui.ScreenSecurity.Platforms.Android;
4 | #elif WINDOWS
5 | using Plugin.Maui.ScreenSecurity.Platforms.Windows;
6 | #endif
7 | using Microsoft.Maui.LifecycleEvents;
8 | using Plugin.Maui.ScreenSecurity.Handlers;
9 |
10 | namespace Plugin.Maui.ScreenSecurity;
11 |
12 | public static class MauiAppBuilderExtensions
13 | {
14 | public static MauiAppBuilder UseScreenSecurity(this MauiAppBuilder builder)
15 | {
16 | builder.ConfigureLifecycleEvents(life =>
17 | {
18 | #if ANDROID34_0_OR_GREATER
19 | life.AddAndroid(android =>
20 | {
21 | if (!OperatingSystem.IsAndroidVersionAtLeast(34))
22 | return;
23 |
24 | CustomScreenCaptureCallback customScreenCaptureCallback = new();
25 | MainThreadExecutor mainThreadExecutor = new();
26 |
27 | android.OnStart(activity =>
28 | {
29 | activity.RegisterScreenCaptureCallback(mainThreadExecutor, customScreenCaptureCallback);
30 | });
31 |
32 | android.OnStop(activity =>
33 | {
34 | activity.UnregisterScreenCaptureCallback(customScreenCaptureCallback);
35 | });
36 |
37 | android.OnDestroy(activity => { });
38 | });
39 | #elif WINDOWS
40 | life.AddWindows(windows =>
41 | {
42 | windows.OnLaunched((app, args) =>
43 | {
44 | var mainWindow = Application.Current?.Windows[0];
45 | var nativeWindow = mainWindow?.Handler?.PlatformView as MauiWinUIWindow;
46 |
47 | NativeMethods.HookID = NativeMethods.SetHook(NativeMethods.Proc);
48 |
49 | if (nativeWindow is not null)
50 | {
51 | nativeWindow.Content.KeyDown += (sender, e) =>
52 | {
53 | if (e.Key == Windows.System.VirtualKey.Snapshot)
54 | {
55 | ScreenCaptureEventHandler.RaiseScreenCaptured();
56 | }
57 | };
58 | }
59 | });
60 |
61 | windows.OnClosed((app, args) =>
62 | {
63 | NativeMethods.UnhookWindowsHookEx(NativeMethods.HookID);
64 | });
65 | });
66 | #endif
67 | });
68 |
69 | builder.Services.AddSingleton(ScreenSecurity.Default);
70 |
71 | return builder;
72 | }
73 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/IScreenSecurity.cs:
--------------------------------------------------------------------------------
1 | #if IOS
2 | using Plugin.Maui.ScreenSecurity.Platforms.iOS;
3 | #endif
4 |
5 | namespace Plugin.Maui.ScreenSecurity;
6 |
7 | public interface IScreenSecurity
8 | {
9 | ///
10 | /// Activates screen security protection when the app is sent
11 | /// to Recents screen or the App Switcher .
12 | /// Also prevents screenshots or screen recordings from being taken.
13 | ///
14 | void ActivateScreenSecurityProtection();
15 |
16 | ///
17 | /// Activates screen security protection when the app is sent
18 | /// to Recents screen or the App Switcher .
19 | /// Also prevents screenshots or screen recordings from being taken.
20 | /// The specified parameters apply to iOS only.
21 | ///
22 | /// Indicates whether to blur the screen.
23 | /// Indicates whether to prevent screenshots.
24 | /// Indicates whether to prevent screen recording.
25 | ///
26 | /// These parameters have no effect on Android and Windows platforms.
27 | ///
28 | void ActivateScreenSecurityProtection(bool blurScreenProtection, bool preventScreenshot, bool preventScreenRecording);
29 |
30 | ///
31 | /// Activates screen security protection when the app is sent
32 | /// to Recents screen or the App Switcher .
33 | /// Also prevents screenshots or screen recordings from being taken.
34 | /// The specified parameters are for using a Color or an Image as protection on iOS only.
35 | ///
36 | ///
37 | /// Provides additional settings for screen security on iOS,
38 | /// allowing customization using either a Color or an Image .
39 | ///
40 | ///
41 | /// These parameters have no effect on Android and Windows platforms.
42 | ///
43 | void ActivateScreenSecurityProtection(ScreenProtectionOptions screenProtectionOptions);
44 |
45 | ///
46 | /// Deactivates all screen security protection.
47 | ///
48 | void DeactivateScreenSecurityProtection();
49 |
50 | ///
51 | /// Indicates whether screen protection is currently enabled.
52 | ///
53 | bool IsProtectionEnabled { get; }
54 |
55 | ///
56 | /// If set to true, exceptions will be thrown when an error occurs.
57 | ///
58 | bool ThrowErrors { set; }
59 |
60 | ///
61 | /// Triggered when the screen is captured, either via screenshot or recording.
62 | ///
63 | event EventHandler? ScreenCaptured;
64 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33516.290
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Maui.ScreenSecurity", "Plugin.Maui.ScreenSecurity\Plugin.Maui.ScreenSecurity.csproj", "{C9913AAB-598A-43EF-93E9-6FA4AE4CCAEC}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F0127D89-F3B5-4C88-9016-5CCA2DBF3F59}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenSecuritySample", "samples\ScreenSecuritySample\ScreenSecuritySample.csproj", "{479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenSecurityBlazorSample", "samples\ScreenSecurityBlazorSample\ScreenSecurityBlazorSample.csproj", "{921C60FE-FBDF-4180-994E-13B6558FD0D9}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {C9913AAB-598A-43EF-93E9-6FA4AE4CCAEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {C9913AAB-598A-43EF-93E9-6FA4AE4CCAEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {C9913AAB-598A-43EF-93E9-6FA4AE4CCAEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {C9913AAB-598A-43EF-93E9-6FA4AE4CCAEC}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
27 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5}.Release|Any CPU.Deploy.0 = Release|Any CPU
30 | {921C60FE-FBDF-4180-994E-13B6558FD0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {921C60FE-FBDF-4180-994E-13B6558FD0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {921C60FE-FBDF-4180-994E-13B6558FD0D9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
33 | {921C60FE-FBDF-4180-994E-13B6558FD0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {921C60FE-FBDF-4180-994E-13B6558FD0D9}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {921C60FE-FBDF-4180-994E-13B6558FD0D9}.Release|Any CPU.Deploy.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(NestedProjects) = preSolution
41 | {479D2E8B-47FB-4F0D-B1AB-3DF4CC7D28A5} = {F0127D89-F3B5-4C88-9016-5CCA2DBF3F59}
42 | {921C60FE-FBDF-4180-994E-13B6558FD0D9} = {F0127D89-F3B5-4C88-9016-5CCA2DBF3F59}
43 | EndGlobalSection
44 | GlobalSection(ExtensibilityGlobals) = postSolution
45 | SolutionGuid = {FB29A9B4-E0B3-4C0E-9E67-D98FC2F3E4EB}
46 | EndGlobalSection
47 | EndGlobal
48 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/ScreenSecurityBlazorSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0-android;net9.0-ios
5 | $(TargetFrameworks);net9.0-windows10.0.19041.0
6 | Exe
7 | ScreenSecurityBlazorSample
8 | true
9 | true
10 | enable
11 | false
12 | enable
13 |
14 |
15 | ScreenSecurityBlazorSample
16 |
17 |
18 | com.companyname.screensecurityblazorsample
19 |
20 |
21 | 1.0
22 | 1
23 |
24 | 15.0
25 | 24.0
26 | 10.0.17763.0
27 | 10.0.17763.0
28 | en
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/ScreenSecuritySample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0-android;net9.0-ios
5 | $(TargetFrameworks);net9.0-windows10.0.19041.0
6 | Exe
7 | ScreenSecuritySample
8 | true
9 | true
10 | enable
11 |
12 |
13 | ScreenSecuritySample
14 |
15 |
16 | com.companyname.screensecuritysample
17 | 0785e1d4-7824-4e6d-9d32-6c3c83aaa9b9
18 |
19 |
20 | 1.0
21 | 1
22 |
23 | 15.0
24 | 24.0
25 | 10.0.17763.0
26 | 10.0.17763.0
27 | en
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | MSBuild:Compile
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Views/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity;
2 |
3 | namespace ScreenSecuritySample.Views;
4 |
5 | public partial class MainPage : ContentPage
6 | {
7 | private readonly IScreenSecurity _screenSecurity;
8 |
9 | private readonly ScreenProtectionOptions _hexProtection = new()
10 | {
11 | HexColor = "#6C4675",
12 | PreventScreenshot = true,
13 | PreventScreenRecording = false
14 | };
15 |
16 | private readonly ScreenProtectionOptions _imageProtection = new()
17 | {
18 | Image = "protection_bg.png",
19 | PreventScreenshot = true,
20 | PreventScreenRecording = true
21 | };
22 |
23 | private bool _hexProtectionEnabled = false;
24 | private bool _imageProtectionEnabled = false;
25 |
26 | public MainPage(IScreenSecurity screenSecurity)
27 | {
28 | InitializeComponent();
29 |
30 | _screenSecurity = screenSecurity;
31 | }
32 |
33 | protected override void OnAppearing()
34 | {
35 | base.OnAppearing();
36 |
37 | // Activate the screen security protection with default settings
38 | _screenSecurity.ActivateScreenSecurityProtection();
39 |
40 | /*
41 | // For changing iOS options, follow one of the next examples:
42 |
43 | // Example 1: Customize with a specific color
44 | _screenSecurity.ActivateScreenSecurityProtection(_hexProtection);
45 | _hexProtectionEnabled = true;
46 |
47 | // Example 2: Customize with an image
48 | _screenSecurity.ActivateScreenSecurityProtection(_imageProtection);
49 | _imageProtectionEnabled = true;
50 | */
51 |
52 | CheckStatusButton();
53 |
54 | isEnabledLabel.Text = $"Screen protection enabled: {_screenSecurity.IsProtectionEnabled}";
55 | }
56 |
57 | protected override void OnDisappearing()
58 | {
59 | _screenSecurity.DeactivateScreenSecurityProtection();
60 |
61 | base.OnDisappearing();
62 | }
63 |
64 | private async void Button_Clicked(object sender, EventArgs e)
65 | {
66 | await Shell.Current.GoToAsync("unprotected_page", true);
67 | }
68 |
69 | private void ActivationBtn_Clicked(object sender, EventArgs e)
70 | {
71 | if (!_screenSecurity.IsProtectionEnabled)
72 | {
73 | var protectionType = (_hexProtectionEnabled, _imageProtectionEnabled) switch
74 | {
75 | (true, false) => _hexProtection,
76 | (false, true) => _imageProtection,
77 | _ => null
78 | };
79 |
80 | if (protectionType is not null)
81 | _screenSecurity.ActivateScreenSecurityProtection(protectionType);
82 | else
83 | _screenSecurity.ActivateScreenSecurityProtection();
84 | }
85 | else
86 | _screenSecurity.DeactivateScreenSecurityProtection();
87 |
88 | CheckStatusButton();
89 |
90 | isEnabledLabel.Text = $"Screen protection enabled: {_screenSecurity.IsProtectionEnabled}";
91 | }
92 |
93 | private void CheckStatusButton()
94 | {
95 | activationBtn.Text = _screenSecurity.IsProtectionEnabled
96 | ? "Deactivate Screen Protection"
97 | : "Activate Screen Protection";
98 | }
99 | }
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/wwwroot/css/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | }
4 |
5 | a, .btn-link {
6 | color: #006bb7;
7 | }
8 |
9 | .btn-primary {
10 | color: #fff;
11 | background-color: #1b6ec2;
12 | border-color: #1861ac;
13 | }
14 |
15 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
16 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
17 | }
18 |
19 | .content {
20 | padding-top: 1.1rem;
21 | }
22 |
23 | h1:focus {
24 | outline: none;
25 | }
26 |
27 | .valid.modified:not([type=checkbox]) {
28 | outline: 1px solid #26b050;
29 | }
30 |
31 | .invalid {
32 | outline: 1px solid #e50000;
33 | }
34 |
35 | .validation-message {
36 | color: #e50000;
37 | }
38 |
39 | #blazor-error-ui {
40 | background: lightyellow;
41 | bottom: 0;
42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
43 | display: none;
44 | left: 0;
45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem;
46 | position: fixed;
47 | width: 100%;
48 | z-index: 1000;
49 | }
50 |
51 | #blazor-error-ui .dismiss {
52 | cursor: pointer;
53 | position: absolute;
54 | right: 0.75rem;
55 | top: 0.5rem;
56 | }
57 |
58 | .blazor-error-boundary {
59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
60 | padding: 1rem 1rem 1rem 3.7rem;
61 | color: white;
62 | }
63 |
64 | .blazor-error-boundary::after {
65 | content: "An error has occurred."
66 | }
67 |
68 | .status-bar-safe-area {
69 | display: none;
70 | }
71 |
72 | @supports (-webkit-touch-callout: none) {
73 | .status-bar-safe-area {
74 | display: flex;
75 | position: sticky;
76 | top: 0;
77 | height: env(safe-area-inset-top);
78 | background-color: #f7f7f7;
79 | width: 100%;
80 | z-index: 1;
81 | }
82 |
83 | .flex-column, .navbar-brand {
84 | padding-left: env(safe-area-inset-left);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Components/Layout/NavMenu.razor.css:
--------------------------------------------------------------------------------
1 | .navbar-toggler {
2 | appearance: none;
3 | cursor: pointer;
4 | width: 3.5rem;
5 | height: 2.5rem;
6 | color: white;
7 | position: absolute;
8 | top: 0.5rem;
9 | right: 1rem;
10 | border: 1px solid rgba(255, 255, 255, 0.1);
11 | background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
12 | }
13 |
14 | .navbar-toggler:checked {
15 | background-color: rgba(255, 255, 255, 0.5);
16 | }
17 |
18 | .top-row {
19 | height: 3.5rem;
20 | background-color: rgba(0,0,0,0.4);
21 | }
22 |
23 | .navbar-brand {
24 | font-size: 1.1rem;
25 | }
26 |
27 | .bi {
28 | display: inline-block;
29 | position: relative;
30 | width: 1.25rem;
31 | height: 1.25rem;
32 | margin-right: 0.75rem;
33 | top: -1px;
34 | background-size: cover;
35 | }
36 |
37 | .bi-house-door-fill-nav-menu {
38 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
39 | }
40 |
41 | .bi-plus-square-fill-nav-menu {
42 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
43 | }
44 |
45 | .bi-list-nested-nav-menu {
46 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
47 | }
48 |
49 | .nav-item {
50 | font-size: 0.9rem;
51 | padding-bottom: 0.5rem;
52 | }
53 |
54 | .nav-item:first-of-type {
55 | padding-top: 1rem;
56 | }
57 |
58 | .nav-item:last-of-type {
59 | padding-bottom: 1rem;
60 | }
61 |
62 | .nav-item ::deep a {
63 | color: #d7d7d7;
64 | border-radius: 4px;
65 | height: 3rem;
66 | display: flex;
67 | align-items: center;
68 | line-height: 3rem;
69 | }
70 |
71 | .nav-item ::deep a.active {
72 | background-color: rgba(255,255,255,0.37);
73 | color: white;
74 | }
75 |
76 | .nav-item ::deep a:hover {
77 | background-color: rgba(255,255,255,0.1);
78 | color: white;
79 | }
80 |
81 | .nav-scrollable {
82 | display: none;
83 | }
84 |
85 | .navbar-toggler:checked ~ .nav-scrollable {
86 | display: block;
87 | }
88 |
89 | @media (min-width: 641px) {
90 | .navbar-toggler {
91 | display: none;
92 | }
93 |
94 | .nav-scrollable {
95 | /* Never collapse the sidebar for wide screens */
96 | display: block;
97 | /* Allow sidebar to scroll for tall menus */
98 | height: calc(100vh - 3.5rem);
99 | overflow-y: auto;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Protections/ImageProtectionManager.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using Plugin.Maui.ScreenSecurity.Handlers;
3 | using UIKit;
4 |
5 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
6 |
7 | internal class ImageProtectionManager
8 | {
9 | #if NET9_0_OR_GREATER
10 | private static readonly Lock _lock = new();
11 | #else
12 | private static readonly object _lock = new();
13 | #endif
14 |
15 | private static bool _enabled;
16 |
17 | private static NSObject? _willResignActiveObserver;
18 | private static NSObject? _didBecomeActiveObserver;
19 |
20 | private static UIImageView? _screenImage = null;
21 |
22 | internal static void HandleImageProtection(bool enabled, bool throwErrors, string image = "", UIWindow? window = null)
23 | {
24 | #if NET9_0_OR_GREATER
25 | lock (_lock)
26 | #else
27 | lock (_lock)
28 | #endif
29 | {
30 | // If state hasn't changed and observers are already set, skip re-subscribing
31 | if (_enabled == enabled
32 | && _willResignActiveObserver is not null
33 | && _didBecomeActiveObserver is not null)
34 | {
35 | return;
36 | }
37 |
38 | _enabled = enabled;
39 |
40 | // Remove existing observers before re-adding
41 | DisposeObservers();
42 |
43 | _willResignActiveObserver = UIApplication.Notifications.ObserveWillResignActive((sender, args) =>
44 | {
45 | try
46 | {
47 | MainThread.BeginInvokeOnMainThread(() =>
48 | {
49 | if (enabled)
50 | EnableImageScreenProtection(image, window);
51 | else
52 | DisableImageScreenProtection();
53 | });
54 | }
55 | catch (Exception ex)
56 | {
57 | ErrorsHandler.HandleException(nameof(HandleImageProtection), throwErrors, ex);
58 | }
59 | });
60 |
61 | _didBecomeActiveObserver = UIApplication.Notifications.ObserveDidBecomeActive((sender, args) =>
62 | {
63 | try
64 | {
65 | MainThread.BeginInvokeOnMainThread(() =>
66 | {
67 | DisableImageScreenProtection();
68 | });
69 | }
70 | catch (Exception ex)
71 | {
72 | ErrorsHandler.HandleException(nameof(HandleImageProtection), throwErrors, ex);
73 | }
74 | });
75 | }
76 | }
77 |
78 | private static void EnableImageScreenProtection(string image, UIWindow? window)
79 | {
80 | if (window is null)
81 | return;
82 |
83 | DisableImageScreenProtection();
84 |
85 | if (string.IsNullOrEmpty(image))
86 | return;
87 |
88 | if (!File.Exists(image))
89 | return;
90 |
91 | MainThread.BeginInvokeOnMainThread(() =>
92 | {
93 | using var uiImage = new UIImage(image);
94 | _screenImage = new UIImageView(UIScreen.MainScreen.Bounds)
95 | {
96 | Image = uiImage,
97 | UserInteractionEnabled = false,
98 | ContentMode = UIViewContentMode.ScaleAspectFill,
99 | ClipsToBounds = true
100 | };
101 |
102 | window.AddSubview(_screenImage);
103 | });
104 | }
105 |
106 | private static void DisableImageScreenProtection()
107 | {
108 | MainThread.BeginInvokeOnMainThread(() =>
109 | {
110 | _screenImage?.RemoveFromSuperview();
111 |
112 | _screenImage?.Dispose();
113 |
114 | _screenImage = null;
115 | });
116 | }
117 |
118 | private static void DisposeObservers()
119 | {
120 | _willResignActiveObserver?.Dispose();
121 | _willResignActiveObserver = null;
122 |
123 | _didBecomeActiveObserver?.Dispose();
124 | _didBecomeActiveObserver = null;
125 | }
126 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Plugin.Maui.ScreenSecurity.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net8.0-android;net8.0-ios;net9.0;net9.0-android;net9.0-ios
5 | $(TargetFrameworks);net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0
6 | true
7 | enable
8 | true
9 | enable
10 | true
11 |
12 | 14.2
13 | 21.0
14 | 10.0.17763.0
15 | 10.0.17763.0
16 |
17 |
18 | True
19 | portable
20 | True
21 | Plugin.Maui.ScreenSecurity
22 | FabriBertani
23 | Safeguard your .NET MAUI app effortlessly by preventing content exposure, screenshots, and recordings with ease.
24 | Copyright 2025 Fabricio Bertani
25 | https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity
26 | https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity
27 | true
28 | git
29 | dotnet-maui;maui;toolkit;security;Plugin.Maui.ScreenSecurity;ScreenSecurity;screen;protection;leak;android;ios
30 | 1.2.3
31 | 1.2.3
32 | README.md
33 | en
34 | LICENSE
35 | True
36 | plugin.maui.screensecurity_128x128.png
37 | 1.2.3
38 | https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/blob/main/CHANGELOG.md
39 | True
40 | snupkg
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | True
63 | \
64 |
65 |
66 | True
67 | \
68 |
69 |
70 | True
71 | \
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Protections/ColorProtectionManager.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using Plugin.Maui.ScreenSecurity.Handlers;
3 | using UIKit;
4 |
5 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
6 |
7 | internal class ColorProtectionManager
8 | {
9 | #if NET9_0_OR_GREATER
10 | private static readonly Lock _lock = new();
11 | #else
12 | private static readonly object _lock = new();
13 | #endif
14 |
15 | private static bool _enabled;
16 |
17 | private static NSObject? _willResignActiveObserver;
18 | private static NSObject? _didBecomeActiveObserver;
19 |
20 | private static UIView? _screenColor = null;
21 |
22 | internal static void HandleColorProtection(bool enabled, bool throwErrors, string hexColor = "", UIWindow? window = null)
23 | {
24 | #if NET9_0_OR_GREATER
25 | lock (_lock)
26 | #else
27 | lock (_lock)
28 | #endif
29 | {
30 | // If state hasn't changed and observers are already set, skip re-subscribing
31 | if (_enabled == enabled
32 | && _willResignActiveObserver is not null
33 | && _didBecomeActiveObserver is not null)
34 | {
35 | return;
36 | }
37 |
38 | _enabled = enabled;
39 |
40 | // Remove existing observers before re-adding
41 | DisposeObservers();
42 |
43 | _willResignActiveObserver = UIApplication.Notifications.ObserveWillResignActive((sender, args) =>
44 | {
45 | try
46 | {
47 | MainThread.BeginInvokeOnMainThread(() =>
48 | {
49 | if (enabled)
50 | EnableColorScreenProtection(window, hexColor);
51 | else
52 | DisableColorScreenProtection();
53 | });
54 | }
55 | catch (Exception ex)
56 | {
57 | ErrorsHandler.HandleException(nameof(HandleColorProtection), throwErrors, ex);
58 | }
59 | });
60 |
61 | _didBecomeActiveObserver = UIApplication.Notifications.ObserveDidBecomeActive((sender, args) =>
62 | {
63 | try
64 | {
65 | MainThread.BeginInvokeOnMainThread(() =>
66 | {
67 | DisableColorScreenProtection();
68 | });
69 | }
70 | catch (Exception ex)
71 | {
72 | ErrorsHandler.HandleException(nameof(HandleColorProtection), throwErrors, ex);
73 | }
74 | });
75 | }
76 | }
77 |
78 | internal static void EnableColor(UIWindow? window, string hexColor, bool throwErrors)
79 | {
80 | try
81 | {
82 | EnableColorScreenProtection(window, hexColor);
83 | }
84 | catch (Exception ex)
85 | {
86 | ErrorsHandler.HandleException(nameof(EnableColor), throwErrors, ex);
87 | }
88 | }
89 |
90 | internal static void DisableColor(bool throwErrors)
91 | {
92 | try
93 | {
94 | DisableColorScreenProtection();
95 | }
96 | catch (Exception ex)
97 | {
98 | ErrorsHandler.HandleException(nameof(DisableColor), throwErrors, ex);
99 | }
100 | }
101 |
102 | private static void EnableColorScreenProtection(UIWindow? window, string hexColor)
103 | {
104 | if (window is null)
105 | return;
106 |
107 | MainThread.BeginInvokeOnMainThread(() =>
108 | {
109 | _screenColor = new UIView(window.Bounds)
110 | {
111 | BackgroundColor = UIColor.Clear.FromHex(hexColor)
112 | };
113 |
114 | window.AddSubview(_screenColor);
115 | });
116 | }
117 |
118 | private static void DisableColorScreenProtection()
119 | {
120 | MainThread.BeginInvokeOnMainThread(() =>
121 | {
122 | _screenColor?.RemoveFromSuperview();
123 |
124 | _screenColor = null;
125 | });
126 | }
127 |
128 | private static void DisposeObservers()
129 | {
130 | _willResignActiveObserver?.Dispose();
131 | _willResignActiveObserver = null;
132 |
133 | _didBecomeActiveObserver?.Dispose();
134 | _didBecomeActiveObserver = null;
135 | }
136 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Protections/ScreenshotProtectionManager.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity.Handlers;
2 | using UIKit;
3 |
4 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
5 |
6 | internal class ScreenshotProtectionManager
7 | {
8 | private static UITextField? _secureTextField = null;
9 | private static UIView? _view = null;
10 |
11 | internal static void HandleScreenshotProtection(bool enabled, bool throwErrors, UIWindow? window = null)
12 | {
13 | try
14 | {
15 | SetScreenshotProtection(enabled, throwErrors, window);
16 | }
17 | catch (Exception ex)
18 | {
19 | ErrorsHandler.HandleException(nameof(HandleScreenshotProtection), throwErrors, ex);
20 | }
21 | }
22 |
23 | private static void SetScreenshotProtection(bool preventScreenshot, bool throwErrors, UIWindow? window)
24 | {
25 | MainThread.BeginInvokeOnMainThread(() =>
26 | {
27 | try
28 | {
29 | if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
30 | {
31 | if (preventScreenshot)
32 | {
33 | if (window is null)
34 | return;
35 |
36 | _secureTextField = new UITextField();
37 |
38 | var color = IOSHelpers.GetCurrentTheme() == ThemeStyle.Light ? UIColor.White : UIColor.Black;
39 |
40 | _view = new UIView(window.Bounds)
41 | {
42 | BackgroundColor = color
43 | };
44 |
45 | _secureTextField.SecureTextEntry = true;
46 |
47 | window.AddSubview(_secureTextField);
48 | window.AddSubview(_view);
49 |
50 | window.Layer.SuperLayer?.AddSublayer(_secureTextField.Layer);
51 | _secureTextField.Layer.Sublayers?.Last().AddSublayer(window.Layer);
52 |
53 | _secureTextField.LeftView = _view;
54 | _secureTextField.LeftViewMode = UITextFieldViewMode.Always;
55 | }
56 | else
57 | {
58 | if (_secureTextField is not null)
59 | _secureTextField.SecureTextEntry = false;
60 |
61 | if (_view is null)
62 | return;
63 |
64 | _view.Layer.RemoveFromSuperLayer();
65 | _view.RemoveFromSuperview();
66 | }
67 | }
68 | else
69 | {
70 | if (window is not null)
71 | {
72 | _secureTextField ??= new()
73 | {
74 | UserInteractionEnabled = false
75 | };
76 |
77 | UIViewController? rootViewController = GetRootPresentedViewController(window);
78 |
79 | rootViewController?.View?.AddSubview(_secureTextField);
80 | window.MakeKeyAndVisible();
81 |
82 | window.Layer.SuperLayer?.AddSublayer(_secureTextField.Layer);
83 |
84 | _secureTextField.Layer.Sublayers?[0].AddSublayer(window.Layer);
85 | }
86 |
87 | if (_secureTextField is null)
88 | return;
89 |
90 | if (preventScreenshot)
91 | _secureTextField.SecureTextEntry = preventScreenshot;
92 | else
93 | {
94 | _secureTextField.SecureTextEntry = false;
95 | _secureTextField = null;
96 | }
97 | }
98 | }
99 | catch (Exception ex)
100 | {
101 | ErrorsHandler.HandleException(nameof(SetScreenshotProtection), throwErrors, ex);
102 | }
103 | });
104 | }
105 |
106 | private static UIViewController? GetRootPresentedViewController(UIWindow window)
107 | {
108 | UIViewController? viewController = window.RootViewController;
109 |
110 | while (viewController?.PresentedViewController is not null)
111 | {
112 | viewController = viewController.PresentedViewController;
113 | }
114 |
115 | return viewController;
116 | }
117 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/Windows/NativeMethods.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity.Handlers;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Plugin.Maui.ScreenSecurity.Platforms.Windows;
6 |
7 | internal partial class NativeMethods
8 | {
9 | #if NET9_0_OR_GREATER
10 | private static readonly Lock _hookLock = new();
11 | #else
12 | private static readonly object _hookLock = new();
13 | #endif
14 |
15 | private const int WH_KEYBOARD_LL = 13;
16 | private const int WM_KEYDOWN = 0x0100;
17 | // Virtual code for Print Screen (PrtSc) key
18 | private const int VK_SNAPSHOT = 0x2C;
19 |
20 | private static int _hookRefCount;
21 |
22 | public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
23 | public static readonly LowLevelKeyboardProc Proc = HookCallback;
24 | public static IntPtr HookID = IntPtr.Zero;
25 |
26 | [LibraryImport("user32.dll", SetLastError = true)]
27 | public static partial uint SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);
28 |
29 | [LibraryImport("user32.dll", EntryPoint = "SetWindowsHookExW", StringMarshalling = StringMarshalling.Utf16)]
30 | private static partial IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
31 |
32 | [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
33 | [return: MarshalAs(UnmanagedType.Bool)]
34 | public static partial bool UnhookWindowsHookEx(IntPtr hhk);
35 |
36 | [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
37 | private static partial IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
38 |
39 | [LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleW", StringMarshalling = StringMarshalling.Utf16)]
40 | private static partial IntPtr GetModuleHandle(string lpModuleName);
41 |
42 | [StructLayout(LayoutKind.Sequential)]
43 | private struct KBDLLHOOKSTRUCT
44 | {
45 | public int vkCode;
46 | public int scanCode;
47 | public int flags;
48 | public int time;
49 | public IntPtr dwExtraInfo;
50 | }
51 |
52 | public static IntPtr SetHook(LowLevelKeyboardProc proc)
53 | {
54 | #if NET9_0_OR_GREATER
55 | lock (_hookLock)
56 | #else
57 | lock (_hookLock)
58 | #endif
59 | {
60 | _hookRefCount++;
61 |
62 | if (HookID != IntPtr.Zero)
63 | return HookID;
64 |
65 | using Process curProcess = Process.GetCurrentProcess();
66 | using ProcessModule? curModule = curProcess.MainModule;
67 |
68 | if (curModule is null)
69 | {
70 | _hookRefCount--;
71 |
72 | return IntPtr.Zero;
73 | }
74 |
75 | HookID = SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
76 |
77 | if (HookID == IntPtr.Zero)
78 | {
79 | int err = Marshal.GetLastWin32Error();
80 |
81 | System.Diagnostics.Trace.TraceError($"SetWindowsHookEx failed. Win32Error={err}");
82 |
83 | _hookRefCount = 0;
84 | }
85 |
86 | return HookID;
87 | }
88 | }
89 |
90 | public static void Unhook()
91 | {
92 | #if NET9_0_OR_GREATER
93 | lock (_hookLock)
94 | #else
95 | lock (_hookLock)
96 | #endif
97 | {
98 | if (_hookRefCount > 0)
99 | _hookRefCount--;
100 |
101 | if (_hookRefCount == 0 && HookID != IntPtr.Zero)
102 | {
103 | if (!UnhookWindowsHookEx(HookID))
104 | {
105 | int err = Marshal.GetLastWin32Error();
106 |
107 | System.Diagnostics.Trace.TraceError($"UnhookWindowsHookEx failed. Win32Error={err}");
108 | }
109 |
110 | HookID = IntPtr.Zero;
111 | }
112 | }
113 | }
114 |
115 | private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
116 | {
117 | try
118 | {
119 | if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
120 | {
121 | int vkCode = Marshal.ReadInt32(lParam);
122 |
123 | if (vkCode == VK_SNAPSHOT)
124 | ScreenCaptureEventHandler.RaiseScreenCaptured();
125 | }
126 | }
127 | catch (Exception ex)
128 | {
129 | System.Diagnostics.Trace.TraceError($"WINDOWS - Exception in HookCallback: {ex}");
130 | }
131 |
132 | return CallNextHookEx(HookID, nCode, wParam, lParam);
133 | }
134 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/Protections/BlurProtectionManager.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using Plugin.Maui.ScreenSecurity.Handlers;
3 | using UIKit;
4 |
5 | namespace Plugin.Maui.ScreenSecurity.Platforms.iOS;
6 |
7 | internal class BlurProtectionManager
8 | {
9 | #if NET9_0_OR_GREATER
10 | private static readonly Lock _lock = new();
11 | #else
12 | private static readonly object _lock = new();
13 | #endif
14 |
15 | private static bool _enabled;
16 |
17 | private static NSObject? _willResignActiveObserver;
18 | private static NSObject? _didBecomeActiveObserver;
19 |
20 | private static UIVisualEffectView? _blurBackground = null;
21 |
22 | internal static void HandleBlurProtection(bool enabled, bool throwErrors, ThemeStyle? style = null, UIWindow? window = null)
23 | {
24 | #if NET9_0_OR_GREATER
25 | lock (_lock)
26 | #else
27 | lock (_lock)
28 | #endif
29 | {
30 | // If state hasn't changed and observers are already set, skip re-subscribing
31 | if (_enabled == enabled
32 | && _willResignActiveObserver is not null
33 | && _didBecomeActiveObserver is not null)
34 | {
35 | return;
36 | }
37 |
38 | _enabled = enabled;
39 |
40 | // Remove existing observers before re-adding
41 | DisposeObservers();
42 |
43 | _willResignActiveObserver = UIApplication.Notifications.ObserveWillResignActive((sender, args) =>
44 | {
45 | try
46 | {
47 | MainThread.BeginInvokeOnMainThread(() =>
48 | {
49 | if (_enabled)
50 | EnableBlurScreenProtection(window, style);
51 | else
52 | DisableBlurScreenProtection(window);
53 | });
54 | }
55 | catch (Exception ex)
56 | {
57 | ErrorsHandler.HandleException(nameof(HandleBlurProtection), throwErrors, ex);
58 | }
59 | });
60 |
61 | _didBecomeActiveObserver = UIApplication.Notifications.ObserveDidBecomeActive((sender, args) =>
62 | {
63 | try
64 | {
65 | MainThread.BeginInvokeOnMainThread(() =>
66 | {
67 | DisableBlurScreenProtection(window);
68 | });
69 | }
70 | catch (Exception ex)
71 | {
72 | ErrorsHandler.HandleException(nameof(HandleBlurProtection), throwErrors, ex);
73 | }
74 | });
75 | }
76 | }
77 |
78 | internal static void EnableBlur(UIWindow? window, ThemeStyle style, bool throwErrors)
79 | {
80 | try
81 | {
82 | EnableBlurScreenProtection(window, style);
83 | }
84 | catch (Exception ex)
85 | {
86 | ErrorsHandler.HandleException(nameof(EnableBlur), throwErrors, ex);
87 | }
88 | }
89 |
90 | internal static void DisableBlur(UIWindow? window, bool throwErrors)
91 | {
92 | try
93 | {
94 | DisableBlurScreenProtection(window);
95 | }
96 | catch (Exception ex)
97 | {
98 | ErrorsHandler.HandleException(nameof(DisableBlur), throwErrors, ex);
99 | }
100 | }
101 |
102 | private static void EnableBlurScreenProtection(UIWindow? window = null, ThemeStyle? style = null)
103 | {
104 | if (window is null)
105 | return;
106 |
107 | MainThread.BeginInvokeOnMainThread(() =>
108 | {
109 | var blurEffectStyle = style switch
110 | {
111 | ThemeStyle.Light => UIBlurEffectStyle.Light,
112 | _ => UIBlurEffectStyle.Dark
113 | };
114 |
115 | using var blurEffect = UIBlurEffect.FromStyle(blurEffectStyle);
116 |
117 | _blurBackground = new UIVisualEffectView(blurEffect)
118 | {
119 | Frame = window.Frame
120 | };
121 |
122 | window.AddSubview(_blurBackground);
123 | });
124 | }
125 |
126 | private static void DisableBlurScreenProtection(UIWindow? window)
127 | {
128 | if (window is null)
129 | return;
130 |
131 | MainThread.BeginInvokeOnMainThread(() =>
132 | {
133 | foreach (var subview in window.Subviews)
134 | {
135 | if (subview is UIVisualEffectView)
136 | {
137 | subview.RemoveFromSuperview();
138 | }
139 | }
140 |
141 | _blurBackground = null;
142 | });
143 | }
144 |
145 | private static void DisposeObservers()
146 | {
147 | _willResignActiveObserver?.Dispose();
148 | _willResignActiveObserver = null;
149 |
150 | _didBecomeActiveObserver?.Dispose();
151 | _didBecomeActiveObserver = null;
152 | }
153 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/Android/ScreenSecurity.android.cs:
--------------------------------------------------------------------------------
1 | using Android.Views;
2 | using Plugin.Maui.ScreenSecurity.Handlers;
3 |
4 | namespace Plugin.Maui.ScreenSecurity;
5 |
6 | internal partial class ScreenSecurityImplementation : IScreenSecurity, IDisposable
7 | {
8 | private bool _disposed;
9 |
10 | public ScreenSecurityImplementation()
11 | {
12 | ScreenCaptureEventHandler.ScreenCaptured += OnScreenCaptured;
13 | }
14 |
15 | ~ScreenSecurityImplementation()
16 | {
17 | Dispose(false);
18 | }
19 |
20 | ///
21 | /// Activates screen security protection when the app is sent
22 | /// to Recents screen or the App Switcher .
23 | /// Also prevents screenshots or screen recordings from being taken.
24 | ///
25 | public void ActivateScreenSecurityProtection()
26 | {
27 | SetScreenSecurityProtection(true);
28 | }
29 |
30 | ///
31 | /// Activates screen security protection when the app is sent
32 | /// to Recents screen or the App Switcher .
33 | /// Also prevents screenshots or screen recordings from being taken.
34 | /// The specified parameters apply to iOS only.
35 | ///
36 | /// Indicates whether to blur the screen.
37 | /// Indicates whether to prevent screenshots.
38 | /// Indicates whether to prevent screen recording.
39 | ///
40 | /// These parameters have no effect on Android and Windows platforms.
41 | ///
42 | public void ActivateScreenSecurityProtection(bool blurScreenProtection, bool preventScreenshot, bool preventScreenRecording)
43 | {
44 | ActivateScreenSecurityProtection();
45 | }
46 |
47 | ///
48 | /// Activates screen security protection when the app is sent
49 | /// to Recents screen or the App Switcher .
50 | /// Also prevents screenshots or screen recordings from being taken.
51 | /// The specified parameters are for using a Color or an Image as protection on iOS only.
52 | ///
53 | ///
54 | /// Provides additional settings for screen security on iOS,
55 | /// allowing customization using either a Color or an Image .
56 | ///
57 | ///
58 | /// These parameters have no effect on Android and Windows platforms.
59 | ///
60 | public void ActivateScreenSecurityProtection(ScreenProtectionOptions screenProtectionOptions)
61 | {
62 | ActivateScreenSecurityProtection();
63 | }
64 |
65 | ///
66 | /// Deactivates all screen security protection.
67 | ///
68 | public void DeactivateScreenSecurityProtection()
69 | {
70 | SetScreenSecurityProtection(false);
71 | }
72 |
73 | private void SetScreenSecurityProtection(bool enabled)
74 | {
75 | MainThread.BeginInvokeOnMainThread(() =>
76 | {
77 | try
78 | {
79 | var activity = Platform.CurrentActivity;
80 |
81 | if (activity is null)
82 | return;
83 |
84 | if (OperatingSystem.IsAndroidVersionAtLeast(33))
85 | activity.SetRecentsScreenshotEnabled(!enabled);
86 |
87 | if (enabled)
88 | activity.Window?.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure);
89 | else
90 | activity.Window?.ClearFlags(WindowManagerFlags.Secure);
91 |
92 | IsProtectionEnabled = enabled;
93 | }
94 | catch (Exception ex)
95 | {
96 | ErrorsHandler.HandleException(nameof(SetScreenSecurityProtection), ThrowErrors, ex);
97 | }
98 | });
99 | }
100 |
101 | private void OnScreenCaptured(object? sender, EventArgs e)
102 | {
103 | ScreenCaptured?.Invoke(this, EventArgs.Empty);
104 | }
105 |
106 | ///
107 | /// Indicates whether screen protection is currently enabled.
108 | ///
109 | public bool IsProtectionEnabled { get; private set; }
110 |
111 | ///
112 | /// If set to true, exceptions will be thrown when an error occurs.
113 | ///
114 | public bool ThrowErrors { get; set; }
115 |
116 | ///
117 | /// Triggered when the screen is captured, either via screenshot or recording.
118 | ///
119 | public event EventHandler? ScreenCaptured;
120 |
121 | #region Disposables
122 |
123 | public void Dispose()
124 | {
125 | Dispose(true);
126 |
127 | GC.SuppressFinalize(this);
128 | }
129 |
130 | protected virtual void Dispose(bool disposing)
131 | {
132 | if (_disposed)
133 | return;
134 |
135 | if (disposing)
136 | ScreenCaptureEventHandler.ScreenCaptured -= OnScreenCaptured;
137 |
138 | _disposed = true;
139 | }
140 |
141 | #endregion
142 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.2.3 (2025/10/09)
4 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.2.2...v1.2.3)
5 |
6 | **Implemented enhancements:**
7 | - Added the `ThrowErrors` property to allow users to catch exceptions with the new error handling implementation.
8 | - Added better hooks, error handling and logging.
9 | - Added support for Windows 10 Version 2004 and above.
10 | - Applied code improvements to the library.
11 | - Updated sample project.
12 | - Changed CHANGELOG date format to **ISO 8601** `(yyyy-MM-dd)`.
13 |
14 | **Fixed bugs:**
15 | - Fixed [#57 (Crash release - Main Thread issue)](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/issues/57)
16 |
17 | ## 1.2.2 (2025/06/02)
18 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.2.1...v1.2.2)
19 |
20 | **Fixed bugs:**
21 | - Fixed a bug that prevented samples projects from compiling.
22 | - Fixed package build to properly include Windows platform.
23 |
24 | ## 1.2.1 (2025/05/25)
25 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.2.0...v1.2.1)
26 |
27 | **Implemented enhancements:**
28 | - Removed .Net7 and added .Net9 support to all platforms.
29 | - Applied code improvements to the library and the sample projects.
30 | - Updated sample projects to .Net9.
31 |
32 | **Fixed bugs:**
33 | - Fixed issue where blur protection was not being disabled.
34 |
35 | ## 1.2.0 (2024/10/14)
36 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.1.6...v1.2.0)
37 |
38 | **Implemented enhancements:**
39 | - Added `Blazor` sample to showcase the implementation of this plugin.
40 | - Added `IsProtectionEnabled` property to check if screen protection is already enabled or disabled.
41 | - Added `ScreenCaptured` event handler, which triggers notifications when a screenshot is taken or the screen is recorded.
42 | - Added plugin initialization.
43 | - Updated sample projects with new implementations.
44 |
45 | **Fixed bugs:**
46 | - Implemented a new screenshot prevention method for iOS 17+.
47 | - Fixed `GetWindow` method on iOS.
48 | - Merged [#39: Bug iOS on Blur](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/pull/39) PR by [fabien367](https://github.com/fabien367).
49 |
50 | ## 1.1.8-beta (2024/05/25)
51 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.1.7-beta...v1.1.8-beta)
52 |
53 | **Implemented enhancements:**
54 | - Added `Blazor` sample to showcase the implementation of this plugin.
55 | - Added `IsProtectionEnabled` property to check if screen protection is already enabled or disabled.
56 |
57 | ## 1.1.7-beta (2024/05/18)
58 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.1.6...v1.1.7-beta)
59 |
60 | **Fixed bugs:**
61 | - Implemented a new screenshot prevention method for iOS 17.
62 | - Fixed `GetWindow` method on iOS.
63 |
64 | ## 1.1.6 (2024/03/18)
65 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.1.5...v1.1.6)
66 |
67 | **Implemented enhancements:**
68 | - Removed .Net6 and added .Net8 support to all platforms.
69 | - Added general code improvements.
70 |
71 | **Fixed bugs:**
72 | - Merged [#22: iOS 17 fix](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/pull/22) PR by [Gogzs](https://github.com/Gogzs).- Fixed screenshot not working on iOS 17+ issue, by changing the screenshot protection implementation, now a blank white or black (depending on the current OS theme) is added before taking the screenshot to cover the screen content.
73 |
74 | ## 1.1.5 (2023/10/24)
75 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.1.0...v1.1.5)
76 |
77 | **Breaking changes:**
78 | - All methods **marked as obsolete** were removed.
79 |
80 | **Implemented enhancements:**
81 | - Added .net6 and .net7 targets.
82 |
83 | ## 1.1.0 (2023/07/21)
84 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.0.0...v1.1.0)
85 |
86 | **Breaking changes:**
87 | - All methods from the previous version **were marked obsolete** and will be removed in the next stable release.
88 |
89 | **Implemented enhancements:**
90 | - Added Windows support.
91 | - Added screenshot prevention for iOS.
92 | - Added unified endpoints to be used on all platforms without preprocessing directives.
93 | - Added .Net6 support to all platforms.
94 |
95 | **Fixed bugs:**
96 | - Fixed Android thread exception: [#6 Exception: Only the original thread that created a view hierarchy can touch its views](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/issues/6)
97 | - Blur screen protection not working properly due to interference with screenshot protection.
98 | - Failed to disable and re-enable the protections due to an iOS layer issue in the screenshot protection.
99 |
100 | ## 1.0.7-beta (2023/07/18)
101 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.0.6-beta...v1.0.7-beta)
102 |
103 | **Implemented enhancements:**
104 | - Added .Net6 support to all platforms.
105 |
106 | ## 1.0.6-beta (2023/07/18)
107 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.0.5-beta...v1.0.6-beta)
108 |
109 | **Fixed bugs:**
110 | - Blur screen protection not working properly due to interference with screenshot protection.
111 | - Failed to disable and re-enable the protections due to an iOS layer issue in the screenshot protection.
112 |
113 | ## 1.0.5-beta (2023/07/17)
114 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.0.4-beta...v1.0.5-beta)
115 |
116 | **Implemented enhancements:**
117 | - Added unified endpoints to be used on all platforms without preprocessing directives.
118 | - Renamed `IOSWindowsHelper` to `IOSHelper` and added the `GetCurrentTheme` method.
119 | - Created `StringsExtensions` helper on iOS to validate strings using regular expressions.
120 | - Created new class name `ScreenProtectionOptions` that will be used as parameter on the new unified api.
121 |
122 | ## 1.0.4-beta (2023/07/12)
123 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.0.3-beta...v1.0.4-beta)
124 |
125 | **Implemented enhancements:**
126 | - Added ErrorsHandler class to avoid repeated code.
127 | - Improved **Android** code by:
128 | - Unifying the methods.
129 | - Implementing SetRecentsScreenshotEnabled method for Android 13 and above.
130 | - Implementing new ErrorsHandler.
131 | - Improved **Windows** code by:
132 | - Unifying the methods.
133 | - Implementing new ErrorsHandler.
134 | - Improved **iOS** code by:
135 | - Splitting each protection.
136 | - Added improvements to overall code and performance.
137 | - Implementing new ErrorsHandler.
138 |
139 |
140 | ## 1.0.3-beta (2023/07/09)
141 | [Full Changelog](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/compare/v1.0.0...v1.0.3-beta)
142 |
143 | **Implemented enhancements:**
144 | - Added screenshot prevention for iOS
145 |
146 | ## 1.0.2-beta (2023/06/03)
147 |
148 | **Fixed bugs:**
149 | - Fixed Android thread exception: [#6 Exception: Only the original thread that created a view hierarchy can touch its views](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/issues/6)
150 |
151 | ## 1.0.1-beta (2023/05/29)
152 |
153 | **Implemented enhancements:**
154 | - Added Windows support
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/Windows/ScreenSecurity.windows.cs:
--------------------------------------------------------------------------------
1 | using Plugin.Maui.ScreenSecurity.Handlers;
2 | using Plugin.Maui.ScreenSecurity.Platforms.Windows;
3 | using System.Runtime.InteropServices;
4 |
5 | using Application = Microsoft.Maui.Controls.Application;
6 |
7 | namespace Plugin.Maui.ScreenSecurity;
8 |
9 | internal partial class ScreenSecurityImplementation : IScreenSecurity, IDisposable
10 | {
11 | private bool _disposed;
12 |
13 | private const uint WDA_NONE = 0x00000000;
14 | private const uint WDA_MONITOR = 0x00000001;
15 |
16 | // Windows 10, version 2004 and later
17 | private const uint WDA_EXCLUDEFROMCAPTURE = 0x00000011;
18 |
19 | #if NET9_0_OR_GREATER
20 | private static readonly Lock _stateLock = new();
21 | #else
22 | private static readonly object _stateLock = new();
23 | #endif
24 |
25 | public ScreenSecurityImplementation()
26 | {
27 | ScreenCaptureEventHandler.ScreenCaptured += OnScreenCaptured;
28 | }
29 |
30 | ~ScreenSecurityImplementation()
31 | {
32 | Dispose(false);
33 | }
34 |
35 | ///
36 | /// Activates screen security protection when the app is sent
37 | /// to Recents screen or the App Switcher .
38 | /// Also prevents screenshots or screen recordings from being taken.
39 | ///
40 | public void ActivateScreenSecurityProtection()
41 | {
42 | #if NET9_0_OR_GREATER
43 | lock (_stateLock)
44 | #else
45 | lock (_stateLock)
46 | #endif
47 | {
48 | if (IsProtectionEnabled)
49 | return;
50 |
51 | MainThread.BeginInvokeOnMainThread(() =>
52 | {
53 | SetScreenshotProtection(true);
54 |
55 | NativeMethods.SetHook(NativeMethods.Proc);
56 | });
57 | }
58 | }
59 |
60 | ///
61 | /// Activates screen security protection when the app is sent
62 | /// to Recents screen or the App Switcher .
63 | /// Also prevents screenshots or screen recordings from being taken.
64 | /// The specified parameters apply to iOS only.
65 | ///
66 | /// Indicates whether to blur the screen.
67 | /// Indicates whether to prevent screenshots.
68 | /// Indicates whether to prevent screen recording.
69 | ///
70 | /// These parameters have no effect on Android and Windows platforms.
71 | ///
72 | public void ActivateScreenSecurityProtection(bool blurScreenProtection, bool preventScreenshot, bool preventScreenRecording)
73 | {
74 | ActivateScreenSecurityProtection();
75 | }
76 |
77 | ///
78 | /// Activates screen security protection when the app is sent
79 | /// to Recents screen or the App Switcher .
80 | /// Also prevents screenshots or screen recordings from being taken.
81 | /// The specified parameters are for using a Color or an Image as protection on iOS only.
82 | ///
83 | ///
84 | /// Provides additional settings for screen security on iOS,
85 | /// allowing customization using either a Color or an Image .
86 | ///
87 | ///
88 | /// These parameters have no effect on Android and Windows platforms.
89 | ///
90 | public void ActivateScreenSecurityProtection(ScreenProtectionOptions screenProtectionOptions)
91 | {
92 | ActivateScreenSecurityProtection();
93 | }
94 |
95 | ///
96 | /// Deactivates all screen security protection.
97 | ///
98 | public void DeactivateScreenSecurityProtection()
99 | {
100 | #if NET9_0_OR_GREATER
101 | lock (_stateLock)
102 | #else
103 | lock (_stateLock)
104 | #endif
105 | {
106 | if (!IsProtectionEnabled)
107 | return;
108 |
109 | MainThread.BeginInvokeOnMainThread(() =>
110 | {
111 | SetScreenshotProtection(false);
112 |
113 | NativeMethods.Unhook();
114 | });
115 | }
116 | }
117 |
118 | ///
119 | /// Indicates whether screen protection is currently enabled.
120 | ///
121 | public bool IsProtectionEnabled { get; private set; }
122 |
123 | ///
124 | /// If set to true, exceptions will be thrown when an error occurs.
125 | ///
126 | public bool ThrowErrors { get; set; }
127 |
128 | ///
129 | /// Triggered when the screen is captured, either via screenshot or recording.
130 | ///
131 | public event EventHandler? ScreenCaptured;
132 |
133 | private void SetScreenshotProtection(bool enabled)
134 | {
135 | try
136 | {
137 | var hwnd = GetWindowHandle();
138 |
139 | if (hwnd == IntPtr.Zero)
140 | {
141 | System.Diagnostics.Trace.TraceWarning("SetScreenshotProtection: Window handle not available.");
142 |
143 | IsProtectionEnabled = enabled;
144 |
145 | return;
146 | }
147 |
148 | if (enabled)
149 | {
150 | if (!ApplyAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE))
151 | {
152 | System.Diagnostics.Trace.TraceInformation("WDA_EXCLUDEFROMCAPTURE not supported; falling back to WDA_MONITOR.");
153 |
154 | ApplyAffinity(hwnd, WDA_MONITOR);
155 | }
156 | }
157 | else
158 | ApplyAffinity(hwnd, WDA_NONE);
159 |
160 | IsProtectionEnabled = enabled;
161 | }
162 | catch (Exception ex)
163 | {
164 | ErrorsHandler.HandleException(nameof(SetScreenshotProtection), ThrowErrors, ex);
165 | }
166 | }
167 |
168 | private static bool ApplyAffinity(IntPtr hwnd, uint affinity)
169 | {
170 | uint result = NativeMethods.SetWindowDisplayAffinity(hwnd, affinity);
171 |
172 | if (result == 0)
173 | {
174 | int err = Marshal.GetLastWin32Error();
175 |
176 | System.Diagnostics.Trace.TraceError($"SetWindowDisplayAffinity failed (Affinity=0x{affinity:X}). Win32Error={err}");
177 |
178 | return false;
179 | }
180 |
181 | return true;
182 | }
183 |
184 | private void OnScreenCaptured(object? sender, EventArgs e)
185 | {
186 | ScreenCaptured?.Invoke(this, EventArgs.Empty);
187 | }
188 |
189 | private static nint GetWindowHandle()
190 | {
191 | if (!MainThread.IsMainThread)
192 | throw new InvalidOperationException("GetWindowHandle must be called on the main (UI) thread.");
193 |
194 | var window = Application.Current?.Windows.Count > 0
195 | ? Application.Current?.Windows[0]
196 | : null;
197 |
198 | var platformView = window?.Handler.PlatformView as MauiWinUIWindow;
199 |
200 | return platformView?.WindowHandle ?? IntPtr.Zero;
201 | }
202 |
203 | #region Disposables
204 |
205 | public void Dispose()
206 | {
207 | Dispose(true);
208 |
209 | GC.SuppressFinalize(this);
210 | }
211 |
212 | protected virtual void Dispose(bool disposing)
213 | {
214 | if (_disposed)
215 | return;
216 |
217 | if (disposing)
218 | ScreenCaptureEventHandler.ScreenCaptured -= OnScreenCaptured;
219 |
220 | _disposed = true;
221 | }
222 |
223 | #endregion
224 | }
--------------------------------------------------------------------------------
/Plugin.Maui.ScreenSecurity/Platforms/iOS/ScreenSecurity.ios.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using Plugin.Maui.ScreenSecurity.Handlers;
3 | using Plugin.Maui.ScreenSecurity.Platforms.iOS;
4 | using UIKit;
5 |
6 | namespace Plugin.Maui.ScreenSecurity;
7 |
8 | internal partial class ScreenSecurityImplementation : IScreenSecurity, IDisposable
9 | {
10 | private UIWindow? _window;
11 |
12 | private NSObject? _screenshotObserver;
13 | private NSObject? _screenCapturedObserver;
14 |
15 | private bool _disposed;
16 |
17 | public ScreenSecurityImplementation()
18 | {
19 | ScreenCaptureEventHandler.ScreenCaptured += OnScreenCaptured;
20 | }
21 |
22 | ~ScreenSecurityImplementation()
23 | {
24 | Dispose(false);
25 | }
26 |
27 | ///
28 | /// Activates screen security protection when the app is sent
29 | /// to Recents screen or the App Switcher .
30 | /// Also prevents screenshots or screen recordings from being taken.
31 | ///
32 | public void ActivateScreenSecurityProtection()
33 | {
34 | GetWindow();
35 |
36 | EnableScreenCapturedEvent();
37 |
38 | BlurProtectionManager.HandleBlurProtection(true, ThrowErrors, IOSHelpers.GetCurrentTheme(), _window);
39 |
40 | HandleScreenCaptureProtection(true, true);
41 |
42 | IsProtectionEnabled = true;
43 | }
44 |
45 | ///
46 | /// Activates screen security protection when the app is sent
47 | /// to Recents screen or the App Switcher .
48 | /// Also prevents screenshots or screen recordings from being taken.
49 | /// The specified parameters apply to iOS only.
50 | ///
51 | /// Indicates whether to blur the screen.
52 | /// Indicates whether to prevent screenshots.
53 | /// Indicates whether to prevent screen recording.
54 | ///
55 | /// These parameters have no effect on Android and Windows platforms.
56 | ///
57 | public void ActivateScreenSecurityProtection(bool blurScreenProtection = true, bool preventScreenshot = true, bool preventScreenRecording = true)
58 | {
59 | GetWindow();
60 |
61 | EnableScreenCapturedEvent();
62 |
63 | if (blurScreenProtection)
64 | BlurProtectionManager.HandleBlurProtection(true, ThrowErrors, IOSHelpers.GetCurrentTheme(), _window);
65 |
66 | HandleScreenCaptureProtection(preventScreenshot, preventScreenRecording);
67 |
68 | IsProtectionEnabled = true;
69 | }
70 |
71 | ///
72 | /// Activates screen security protection when the app is sent
73 | /// to Recents screen or the App Switcher .
74 | /// Also prevents screenshots or screen recordings from being taken.
75 | /// The specified parameters are for using a Color or an Image as protection on iOS only.
76 | ///
77 | ///
78 | /// Provides additional settings for screen security on iOS,
79 | /// allowing customization using either a Color or an Image .
80 | ///
81 | ///
82 | /// These parameters have no effect on Android and Windows platforms.
83 | ///
84 | public void ActivateScreenSecurityProtection(ScreenProtectionOptions screenProtectionOptions)
85 | {
86 | GetWindow();
87 |
88 | EnableScreenCapturedEvent();
89 |
90 | if (!string.IsNullOrEmpty(screenProtectionOptions.HexColor)
91 | && string.IsNullOrEmpty(screenProtectionOptions.Image))
92 | {
93 | if (screenProtectionOptions.HexColor.IsHexColor())
94 | ColorProtectionManager.HandleColorProtection(true, ThrowErrors, screenProtectionOptions.HexColor, _window);
95 | else
96 | {
97 | var invalidHexadecimalColorMessage = $"{screenProtectionOptions.HexColor} is not a valid hexadecimal color.";
98 |
99 | System.Diagnostics.Trace.TraceError(invalidHexadecimalColorMessage);
100 |
101 | if (ThrowErrors)
102 | throw new ArgumentException(invalidHexadecimalColorMessage);
103 | }
104 | }
105 | else if (!string.IsNullOrEmpty(screenProtectionOptions.Image)
106 | && string.IsNullOrEmpty(screenProtectionOptions.HexColor))
107 | {
108 | if (screenProtectionOptions.Image.IsValidImage())
109 | ImageProtectionManager.HandleImageProtection(true, ThrowErrors, screenProtectionOptions.Image, _window);
110 | else
111 | {
112 | var invalidImageFormatMessage = $"{screenProtectionOptions.Image} is not a valid image format.";
113 |
114 | System.Diagnostics.Trace.TraceError(invalidImageFormatMessage);
115 |
116 | if (ThrowErrors)
117 | throw new ArgumentException(invalidImageFormatMessage);
118 | }
119 | }
120 |
121 | HandleScreenCaptureProtection(screenProtectionOptions.PreventScreenshot, screenProtectionOptions.PreventScreenRecording);
122 |
123 | IsProtectionEnabled = true;
124 | }
125 |
126 | ///
127 | /// Deactivates all screen security protection.
128 | ///
129 | public void DeactivateScreenSecurityProtection()
130 | {
131 | BlurProtectionManager.HandleBlurProtection(false, ThrowErrors);
132 |
133 | ColorProtectionManager.HandleColorProtection(false, ThrowErrors);
134 |
135 | ImageProtectionManager.HandleImageProtection(false, ThrowErrors);
136 |
137 | ScreenRecordingProtectionManager.HandleScreenRecordingProtection(false, ThrowErrors);
138 |
139 | ScreenshotProtectionManager.HandleScreenshotProtection(false, ThrowErrors);
140 |
141 | IsProtectionEnabled = false;
142 | }
143 |
144 | ///
145 | /// Indicates whether screen protection is currently enabled.
146 | ///
147 | public bool IsProtectionEnabled { get; private set; }
148 |
149 | ///
150 | /// If set to true, exceptions will be thrown when an error occurs.
151 | ///
152 | public bool ThrowErrors { get; set; }
153 |
154 | ///
155 | /// Triggered when the screen is captured, either via screenshot or recording.
156 | ///
157 | public event EventHandler? ScreenCaptured;
158 |
159 | private void HandleScreenCaptureProtection(bool preventScreenshot, bool preventScreenRecording)
160 | {
161 | ScreenshotProtectionManager.HandleScreenshotProtection(preventScreenshot, ThrowErrors, _window);
162 |
163 | ScreenRecordingProtectionManager.HandleScreenRecordingProtection(preventScreenRecording, ThrowErrors, string.Empty, _window);
164 | }
165 |
166 | private void EnableScreenCapturedEvent()
167 | {
168 | _screenshotObserver = UIApplication.Notifications.ObserveUserDidTakeScreenshot((sender, args) =>
169 | {
170 | ScreenCaptureEventHandler.RaiseScreenCaptured();
171 | });
172 |
173 | _screenCapturedObserver = UIScreen.Notifications.ObserveCapturedDidChange((sender, args) =>
174 | {
175 | ScreenCaptureEventHandler.RaiseScreenCaptured();
176 | });
177 | }
178 |
179 | ///
180 | /// Ensures that _window is initialized with the current UIWindow instance.
181 | /// If called from the main thread, the window is set synchronously.
182 | /// If called from a background thread, the assignment is dispatched asynchronously to the main thread.
183 | /// Note: If you call this method from a background thread, _window may not be immediately available
184 | /// after the call returns, as the assignment happens asynchronously. Any code that depends on _window
185 | /// being set should either ensure it runs on the main thread or handle the possibility that _window is still null.
186 | ///
187 | private void GetWindow()
188 | {
189 | if (_window is not null)
190 | return;
191 |
192 | if (MainThread.IsMainThread)
193 | {
194 | _window = IOSHelpers.GetWindow();
195 | }
196 | else
197 | {
198 | MainThread.BeginInvokeOnMainThread(() =>
199 | {
200 | _window ??= IOSHelpers.GetWindow();
201 | });
202 | }
203 | }
204 |
205 | private void OnScreenCaptured(object? sender, EventArgs e)
206 | {
207 | ScreenCaptured?.Invoke(this, EventArgs.Empty);
208 | }
209 |
210 | #region Disposables
211 |
212 | public void Dispose()
213 | {
214 | Dispose(true);
215 |
216 | GC.SuppressFinalize(this);
217 | }
218 |
219 | protected virtual void Dispose(bool disposing)
220 | {
221 | if (_disposed)
222 | return;
223 |
224 | if (disposing)
225 | {
226 | ScreenCaptureEventHandler.ScreenCaptured -= OnScreenCaptured;
227 |
228 | _screenshotObserver?.Dispose();
229 | _screenshotObserver = null;
230 |
231 | _screenCapturedObserver?.Dispose();
232 | _screenCapturedObserver = null;
233 | }
234 |
235 | _disposed = true;
236 | }
237 |
238 | #endregion
239 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Plugin.Maui.ScreenSecurity
4 | [](https://www.nuget.org/packages/Plugin.Maui.ScreenSecurity) [](https://www.nuget.org/packages/Plugin.Maui.ScreenSecurity/#versions-body-tab) [](https://buymeacoffee.com/fabribertani)
5 |
6 | `Plugin.Maui.ScreenSecurity` provides a seamless solution for preventing content exposure, as well as blocking screenshots and recordings within your .NET MAUI application
7 |
8 | ## Platforms supported
9 | | Platform | Version |
10 | |-------------------|:-----------:|
11 | | .Net MAUI Android | API 21+ |
12 | | .Net MAUI iOS. | iOS 14.2+ |
13 | | Windows | 10.0.17763+ |
14 |
15 | ## Version 1.2.3
16 |
17 | ### What's new?
18 | - Added the `ThrowErrors` property to allow users to catch exceptions with the new error handling implementation.
19 | - Added better hooks, error handling and logging.
20 | - Added support for Windows 10 Version 2004 and above.
21 | - Applied code improvements to the library.
22 | - Updated sample project.
23 | - Changed CHANGELOG date format to **ISO 8601** `(yyyy-MM-dd)`.
24 | - Fixed the issue where the screen protection code was not running on the main thread.
25 |
26 | Click [here](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/releases/tag/v1.2.3) to see the full Changelog!
27 |
28 | ## Installation
29 | `Plugin.Maui.ScreenSecurity` is available via NuGet, grab the latest package and install it on your solution:
30 |
31 | dotnet add package Plugin.Maui.ScreenSecurity --version 1.2.3
32 |
33 | Initialize the plugin in your `MauiProgram` class:
34 |
35 | ```csharp
36 | using Plugin.Maui.ScreenSecurity;
37 |
38 | public static MauiApp CreateMauiApp()
39 | {
40 | var builder = MauiApp.CreateBuilder();
41 |
42 | builder
43 | .UseMauiApp()
44 | .ConfigureFonts(fonts =>
45 | {
46 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
47 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
48 | })
49 | .UseScreenSecurity();
50 |
51 | return builder.Build();
52 | }
53 | ```
54 |
55 | ### Android
56 |
57 | In your `Android.manifest` file (Platforms/Android) add the following permission:
58 |
59 | ```xml
60 |
61 | ```
62 |
63 | ## Using Plugin.Maui.ScreenSecurity
64 |
65 | ## :warning: WARNING :warning:
66 | It's important to acknowledge that preventing users from taking screenshots or recordings of your app can be a challenging task and achieving complete prevention may not be feasible. It's worth noting that no method can entirely eliminate the possibility of your screen being captured through another physical device or a potential breach in the OS.
67 |
68 | :point_right: It's also important to consider the impact on user experience when implementing any of these methods and striking a balance with the security concerns of your app.
69 |
70 | ---
71 |
72 | ## API Usage
73 | > If you are still using version 1.0.0, please refer to the [Legacy docs](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/wiki/Legacy) for the previous version.
74 |
75 | The new unified API includes two methods: `ActivateScreenSecurityProtection()` and `DeactivateScreenSecurityProtection()`, with optional parameters applicable only to the iOS platform. It also provides two properties: `IsProtectionEnabled`, which checks if protection is active, and the `ScreenCaptured` event handler, which notifies when a screenshot is taken or the screen is recorded.
76 |
77 | ```csharp
78 | void ActivateScreenSecurityProtection();
79 | ```
80 | When you activate this protection, your app's content will be safeguarded when it's sent to the Recents screen or the App Switcher. This helps ensure that sensitive information won't be exposed.
81 |
82 | ### Behavior by platform:
83 | - **Android**: provides enhanced protection for screen content by preventing exposure when the app is sent to the _Background_ or displayed on the _Recents_ screen. It also effectively prevents unauthorized screenshots or recordings from being captured.
84 | - **Windows**: Prevents screenshots and recordings by obscuring the screen of the app.
85 | - **iOS**: By default, it apply a blur layer when the app is sent to the _Background_ or displayed on the _App Switcher_. Also enables the screenshot and screen recording protection.
86 |
87 | ```csharp
88 | void ActivateScreenSecurityProtection(bool blurScreenProtection, bool preventScreenshot, bool preventScreenRecording);
89 | ```
90 | This method is similar to the previous method, but with parameters to change the default values in iOS:
91 |
92 | - **`blurScreenProtection`**: Enable/disable screen blurring to prevent content visibility in the background. **True** by default.
93 | - **`preventScreenshot`**: Decide whether users can take screenshots of your app. **True** by default.
94 | - **`preventScreenRecording`**: Control whether users can record the screen while using your app. **True** by default.
95 |
96 | ```csharp
97 | void ActivateScreenSecurityProtection(ScreenProtectionOptions screenProtectionOptions);
98 | ```
99 | This method is similar to the original method, but takes a `ScreenProtectionOptions` parameter. This allows you to further customize the screen protection by specifying either a _Color_ or an _Image_, along with the the screenshot and screen recording protection for iOS devices.
100 |
101 | **Note**: If you set both _Color_ and _Image_, it will only apply the one you declared first.
102 |
103 | `ScreenProtectionOptions` properties:
104 | - **`Color`**: Represents a color in the form of a hexadecimal string and can be passed as an argument to customize the color layer. It supports formats such as `#RGB`, `#RGBA`, `#RRGGBB`, or `#RRGGBBAA`. **Empty** by default.
105 | - **`Image`**: The name of the image file along with its extension. In order to utilize this property, please follow these steps:
106 | - Save the image you intend to use inside the `Resources\Images` folder.
107 | - Ensure you refer to the [.Net MAUI Image documentation](https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/image#load-a-local-image) for detailed instructions on how to accomplish this task effectively.
108 | - :warning: If your app does not recognize the image after setting the build action to `MauiImage`, consider changing the build action to `Embedded resource` to ensure proper functionality.
109 | - **`PreventScreenshot`**: Decide whether users can take screenshots of your app. **True** by default.
110 | - **`PreventScreenRecording`**: Control whether users can record the screen while using your app. **True** by default.
111 |
112 | ```csharp
113 | void DeactivateScreenSecurityProtection();
114 | ```
115 | This method deactivates all screen security protection.
116 |
117 | ```csharp
118 | bool IsProtectionEnabled { get; }
119 | ```
120 | This bool checks if screen protection is enabled.
121 |
122 | ```csharp
123 | ThrowErrors
124 | ```
125 | This will allow users to catch exceptions with the new error handling implementation.
126 |
127 | ```csharp
128 | event EventHandler? ScreenCaptured;
129 | ```
130 | The event handler is triggered when the screen is captured, either through a screenshot or recording on Android and iOS, **but only for screenshots on Windows**.
131 |
132 | ## Usage Example
133 |
134 | ```csharp
135 | public partial class MainPage : ContentPage
136 | {
137 | private readonly IScreenSecurity _screenSecurity;
138 |
139 | public MainPage(IScreenSecurity screenSecurity)
140 | {
141 | InitializeComponent();
142 |
143 | _screenSecurity = screenSecurity;
144 |
145 | // Set to true if you want to catch exceptions.
146 | _screenSecurity.ThrowErrors = true;
147 | }
148 |
149 | protected override void OnAppearing()
150 | {
151 | base.OnAppearing();
152 |
153 | // Check if screen security protection is not enabled
154 | if (!_screenSecurity.IsProtectionEnabled)
155 | {
156 | try
157 | {
158 | // Activate the screen security protection with default settings
159 | _screenSecurity.ActivateScreenSecurityProtection();
160 | }
161 | catch (Exception ex)
162 | {
163 | // Handle the exception here...
164 | }
165 | }
166 |
167 | // Attach to the ScreenCaptured event handler
168 | _screenSecurity.ScreenCaptured += OnScreenCaptured;
169 |
170 | /*
171 | // For changing iOS options, follow one of the next examples:
172 |
173 | // Example 1: Customize with a specific color
174 | var screenProtectionOptions = new ScreenProtectionOptions
175 | {
176 | HexColor = "#6C4675",
177 | PreventScreenshot = true,
178 | PreventScreenRecording = false
179 | };
180 |
181 | // Example 2: Customize with an image
182 | var screenProtectionOptions = new ScreenProtectionOptions
183 | {
184 | Image = "protection_bg.png"
185 | PreventScreenshot = false,
186 | PreventScreenRecording = true
187 | };
188 |
189 | _screenSecurity.ActivateScreenSecurityProtection(screenProtectionOptions);
190 | */
191 | }
192 |
193 | protected override void OnDisappearing()
194 | {
195 | _screenSecurity.DeactivateScreenSecurityProtection();
196 |
197 | // Detach from the ScreenCaptured event handler
198 | _screenSecurity.ScreenCaptured -= OnScreenCaptured;
199 |
200 | base.OnDisappearing();
201 | }
202 |
203 | private async void OnScreenCaptured(object sender, EventArgs e)
204 | {
205 | string title = "ScreenSecuritySample";
206 | string message = "Screen was captured by screenshot or recording.";
207 |
208 | await Shell.Current.DisplayAlert(title, message, "Ok");
209 | }
210 | }
211 | ```
212 |
213 | ## Sample
214 | Refer to the [samples folder](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/tree/main/samples) for a detailed implementation of this plugin for both Maui and Blazor, which will give you a complete understanding of its usage.
215 |
216 | ## Contributions
217 | Feel free to open an [Issue](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/issues) if you encounter any bugs or submit a PR to contribute improvements or fixes. Your contributions are greatly appreciated.
218 |
219 | ## License
220 | Plugin.Maui.ScreenSecurity is licensed under [MIT](https://github.com/FabriBertani/Plugin.Maui.ScreenSecurity/blob/main/LICENSE) license.
221 |
222 | ## Contributors
223 |
224 | * **[Goran Karacic](https://github.com/Gogzs)** for the iOS 17 fix.
225 | * **[fabien367](https://github.com/fabien367)** for the iOS leak fix.
226 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudio,rider,visualstudiocode
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,rider,visualstudiocode
3 |
4 | ### Rider ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/**/usage.statistics.xml
12 | .idea/**/dictionaries
13 | .idea/**/shelf
14 | .idea/.idea.*
15 |
16 | # AWS User-specific
17 | .idea/**/aws.xml
18 |
19 | # Generated files
20 | .idea/**/contentModel.xml
21 |
22 | # Sensitive or high-churn files
23 | .idea/**/dataSources/
24 | .idea/**/dataSources.ids
25 | .idea/**/dataSources.local.xml
26 | .idea/**/sqlDataSources.xml
27 | .idea/**/dynamic.xml
28 | .idea/**/uiDesigner.xml
29 | .idea/**/dbnavigator.xml
30 |
31 | # Gradle
32 | .idea/**/gradle.xml
33 | .idea/**/libraries
34 |
35 | # Gradle and Maven with auto-import
36 | # When using Gradle or Maven with auto-import, you should exclude module files,
37 | # since they will be recreated, and may cause churn. Uncomment if using
38 | # auto-import.
39 | # .idea/artifacts
40 | # .idea/compiler.xml
41 | # .idea/jarRepositories.xml
42 | # .idea/modules.xml
43 | # .idea/*.iml
44 | # .idea/modules
45 | # *.iml
46 | # *.ipr
47 |
48 | # CMake
49 | cmake-build-*/
50 |
51 | # Mongo Explorer plugin
52 | .idea/**/mongoSettings.xml
53 |
54 | # File-based project format
55 | *.iws
56 |
57 | # IntelliJ
58 | out/
59 |
60 | # mpeltonen/sbt-idea plugin
61 | .idea_modules/
62 |
63 | # JIRA plugin
64 | atlassian-ide-plugin.xml
65 |
66 | # Cursive Clojure plugin
67 | .idea/replstate.xml
68 |
69 | # SonarLint plugin
70 | .idea/sonarlint/
71 |
72 | # Crashlytics plugin (for Android Studio and IntelliJ)
73 | com_crashlytics_export_strings.xml
74 | crashlytics.properties
75 | crashlytics-build.properties
76 | fabric.properties
77 |
78 | # Editor-based Rest Client
79 | .idea/httpRequests
80 |
81 | # Android studio 3.1+ serialized cache file
82 | .idea/caches/build_file_checksums.ser
83 |
84 | ### VisualStudioCode ###
85 | .vscode/*
86 | !.vscode/settings.json
87 | !.vscode/tasks.json
88 | !.vscode/launch.json
89 | !.vscode/extensions.json
90 | !.vscode/*.code-snippets
91 |
92 | # Local History for Visual Studio Code
93 | .history/
94 |
95 | # Built Visual Studio Code Extensions
96 | *.vsix
97 |
98 | ### VisualStudioCode Patch ###
99 | # Ignore all local history of files
100 | .history
101 | .ionide
102 |
103 | ### VisualStudio ###
104 | ## Ignore Visual Studio temporary files, build results, and
105 | ## files generated by popular Visual Studio add-ons.
106 | ##
107 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
108 |
109 | # User-specific files
110 | *.rsuser
111 | *.suo
112 | *.user
113 | *.userosscache
114 | *.sln.docstates
115 |
116 | # User-specific files (MonoDevelop/Xamarin Studio)
117 | *.userprefs
118 |
119 | # Mono auto generated files
120 | mono_crash.*
121 |
122 | # Build results
123 | [Dd]ebug/
124 | [Dd]ebugPublic/
125 | [Rr]elease/
126 | [Rr]eleases/
127 | x64/
128 | x86/
129 | [Ww][Ii][Nn]32/
130 | [Aa][Rr][Mm]/
131 | [Aa][Rr][Mm]64/
132 | bld/
133 | [Bb]in/
134 | [Oo]bj/
135 | [Ll]og/
136 | [Ll]ogs/
137 |
138 | # Visual Studio 2015/2017 cache/options directory
139 | .vs/
140 | # Uncomment if you have tasks that create the project's static files in wwwroot
141 | #wwwroot/
142 |
143 | # Visual Studio 2017 auto generated files
144 | Generated\ Files/
145 |
146 | # MSTest test Results
147 | [Tt]est[Rr]esult*/
148 | [Bb]uild[Ll]og.*
149 |
150 | # NUnit
151 | *.VisualState.xml
152 | TestResult.xml
153 | nunit-*.xml
154 |
155 | # Build Results of an ATL Project
156 | [Dd]ebugPS/
157 | [Rr]eleasePS/
158 | dlldata.c
159 |
160 | # Benchmark Results
161 | BenchmarkDotNet.Artifacts/
162 |
163 | # .NET Core
164 | project.lock.json
165 | project.fragment.lock.json
166 | artifacts/
167 |
168 | # ASP.NET Scaffolding
169 | ScaffoldingReadMe.txt
170 |
171 | # StyleCop
172 | StyleCopReport.xml
173 |
174 | # Files built by Visual Studio
175 | *_i.c
176 | *_p.c
177 | *_h.h
178 | *.ilk
179 | *.meta
180 | *.obj
181 | *.iobj
182 | *.pch
183 | *.pdb
184 | *.ipdb
185 | *.pgc
186 | *.pgd
187 | *.rsp
188 | *.sbr
189 | *.tlb
190 | *.tli
191 | *.tlh
192 | *.tmp
193 | *.tmp_proj
194 | *_wpftmp.csproj
195 | *.log
196 | *.tlog
197 | *.vspscc
198 | *.vssscc
199 | .builds
200 | *.pidb
201 | *.svclog
202 | *.scc
203 |
204 | # Chutzpah Test files
205 | _Chutzpah*
206 |
207 | # Visual C++ cache files
208 | ipch/
209 | *.aps
210 | *.ncb
211 | *.opendb
212 | *.opensdf
213 | *.sdf
214 | *.cachefile
215 | *.VC.db
216 | *.VC.VC.opendb
217 |
218 | # Visual Studio profiler
219 | *.psess
220 | *.vsp
221 | *.vspx
222 | *.sap
223 |
224 | # Visual Studio Trace Files
225 | *.e2e
226 |
227 | # TFS 2012 Local Workspace
228 | $tf/
229 |
230 | # Guidance Automation Toolkit
231 | *.gpState
232 |
233 | # ReSharper is a .NET coding add-in
234 | _ReSharper*/
235 | *.[Rr]e[Ss]harper
236 | *.DotSettings.user
237 |
238 | # TeamCity is a build add-in
239 | _TeamCity*
240 |
241 | # DotCover is a Code Coverage Tool
242 | *.dotCover
243 |
244 | # AxoCover is a Code Coverage Tool
245 | .axoCover/*
246 | !.axoCover/settings.json
247 |
248 | # Coverlet is a free, cross platform Code Coverage Tool
249 | coverage*.json
250 | coverage*.xml
251 | coverage*.info
252 |
253 | # Visual Studio code coverage results
254 | *.coverage
255 | *.coveragexml
256 |
257 | # NCrunch
258 | _NCrunch_*
259 | .*crunch*.local.xml
260 | nCrunchTemp_*
261 |
262 | # MightyMoose
263 | *.mm.*
264 | AutoTest.Net/
265 |
266 | # Web workbench (sass)
267 | .sass-cache/
268 |
269 | # Installshield output folder
270 | [Ee]xpress/
271 |
272 | # DocProject is a documentation generator add-in
273 | DocProject/buildhelp/
274 | DocProject/Help/*.HxT
275 | DocProject/Help/*.HxC
276 | DocProject/Help/*.hhc
277 | DocProject/Help/*.hhk
278 | DocProject/Help/*.hhp
279 | DocProject/Help/Html2
280 | DocProject/Help/html
281 |
282 | # Click-Once directory
283 | publish/
284 |
285 | # Publish Web Output
286 | *.[Pp]ublish.xml
287 | *.azurePubxml
288 | # Note: Comment the next line if you want to checkin your web deploy settings,
289 | # but database connection strings (with potential passwords) will be unencrypted
290 | *.pubxml
291 | *.publishproj
292 |
293 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
294 | # checkin your Azure Web App publish settings, but sensitive information contained
295 | # in these scripts will be unencrypted
296 | PublishScripts/
297 |
298 | # NuGet Packages
299 | *.nupkg
300 | # NuGet Symbol Packages
301 | *.snupkg
302 | # The packages folder can be ignored because of Package Restore
303 | **/[Pp]ackages/*
304 | # except build/, which is used as an MSBuild target.
305 | !**/[Pp]ackages/build/
306 | # Uncomment if necessary however generally it will be regenerated when needed
307 | #!**/[Pp]ackages/repositories.config
308 | # NuGet v3's project.json files produces more ignorable files
309 | *.nuget.props
310 | *.nuget.targets
311 |
312 | # Microsoft Azure Build Output
313 | csx/
314 | *.build.csdef
315 |
316 | # Microsoft Azure Emulator
317 | ecf/
318 | rcf/
319 |
320 | # Windows Store app package directories and files
321 | AppPackages/
322 | BundleArtifacts/
323 | Package.StoreAssociation.xml
324 | _pkginfo.txt
325 | *.appx
326 | *.appxbundle
327 | *.appxupload
328 |
329 | # Visual Studio cache files
330 | # files ending in .cache can be ignored
331 | *.[Cc]ache
332 | # but keep track of directories ending in .cache
333 | !?*.[Cc]ache/
334 |
335 | # Others
336 | ClientBin/
337 | ~$*
338 | *~
339 | *.dbmdl
340 | *.dbproj.schemaview
341 | *.jfm
342 | *.pfx
343 | *.publishsettings
344 | orleans.codegen.cs
345 |
346 | # Including strong name files can present a security risk
347 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
348 | #*.snk
349 |
350 | # Since there are multiple workflows, uncomment next line to ignore bower_components
351 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
352 | #bower_components/
353 |
354 | # RIA/Silverlight projects
355 | Generated_Code/
356 |
357 | # Backup & report files from converting an old project file
358 | # to a newer Visual Studio version. Backup files are not needed,
359 | # because we have git ;-)
360 | _UpgradeReport_Files/
361 | Backup*/
362 | UpgradeLog*.XML
363 | UpgradeLog*.htm
364 | ServiceFabricBackup/
365 | *.rptproj.bak
366 |
367 | # SQL Server files
368 | *.mdf
369 | *.ldf
370 | *.ndf
371 |
372 | # Business Intelligence projects
373 | *.rdl.data
374 | *.bim.layout
375 | *.bim_*.settings
376 | *.rptproj.rsuser
377 | *- [Bb]ackup.rdl
378 | *- [Bb]ackup ([0-9]).rdl
379 | *- [Bb]ackup ([0-9][0-9]).rdl
380 |
381 | # Microsoft Fakes
382 | FakesAssemblies/
383 |
384 | # GhostDoc plugin setting file
385 | *.GhostDoc.xml
386 |
387 | # Node.js Tools for Visual Studio
388 | .ntvs_analysis.dat
389 | node_modules/
390 |
391 | # Visual Studio 6 build log
392 | *.plg
393 |
394 | # Visual Studio 6 workspace options file
395 | *.opt
396 |
397 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
398 | *.vbw
399 |
400 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
401 | *.vbp
402 |
403 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
404 | *.dsw
405 | *.dsp
406 |
407 | # Visual Studio 6 technical files
408 |
409 | # Visual Studio LightSwitch build output
410 | **/*.HTMLClient/GeneratedArtifacts
411 | **/*.DesktopClient/GeneratedArtifacts
412 | **/*.DesktopClient/ModelManifest.xml
413 | **/*.Server/GeneratedArtifacts
414 | **/*.Server/ModelManifest.xml
415 | _Pvt_Extensions
416 |
417 | # Paket dependency manager
418 | .paket/paket.exe
419 | paket-files/
420 |
421 | # FAKE - F# Make
422 | .fake/
423 |
424 | # CodeRush personal settings
425 | .cr/personal
426 |
427 | # Python Tools for Visual Studio (PTVS)
428 | __pycache__/
429 | *.pyc
430 |
431 | # Cake - Uncomment if you are using it
432 | # tools/**
433 | # !tools/packages.config
434 |
435 | # Tabs Studio
436 | *.tss
437 |
438 | # Telerik's JustMock configuration file
439 | *.jmconfig
440 |
441 | # BizTalk build output
442 | *.btp.cs
443 | *.btm.cs
444 | *.odx.cs
445 | *.xsd.cs
446 |
447 | # OpenCover UI analysis results
448 | OpenCover/
449 |
450 | # Azure Stream Analytics local run output
451 | ASALocalRun/
452 |
453 | # MSBuild Binary and Structured Log
454 | *.binlog
455 |
456 | # NVidia Nsight GPU debugger configuration file
457 | *.nvuser
458 |
459 | # MFractors (Xamarin productivity tool) working folder
460 | .mfractor/
461 |
462 | # Local History for Visual Studio
463 | .localhistory/
464 |
465 | # Visual Studio History (VSHistory) files
466 | .vshistory/
467 |
468 | # BeatPulse healthcheck temp database
469 | healthchecksdb
470 |
471 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
472 | MigrationBackup/
473 |
474 | # Ionide (cross platform F# VS Code tools) working folder
475 | .ionide/
476 |
477 | # Fody - auto-generated XML schema
478 | FodyWeavers.xsd
479 |
480 | # VS Code files for those working on multiple tools
481 | *.code-workspace
482 |
483 | # Local History for Visual Studio Code
484 |
485 | # Windows Installer files from build outputs
486 | *.cab
487 | *.msi
488 | *.msix
489 | *.msm
490 | *.msp
491 |
492 | # JetBrains Rider
493 | *.sln.iml
494 |
495 | ### VisualStudio Patch ###
496 | # Additional files built by Visual Studio
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Images/dotnet_bot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/samples/ScreenSecurityBlazorSample/Resources/Images/dotnet_bot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/samples/ScreenSecuritySample/Resources/Styles/Styles.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
15 |
16 |
21 |
22 |
25 |
26 |
49 |
50 |
67 |
68 |
88 |
89 |
110 |
111 |
132 |
133 |
138 |
139 |
159 |
160 |
178 |
179 |
183 |
184 |
206 |
207 |
222 |
223 |
243 |
244 |
247 |
248 |
271 |
272 |
292 |
293 |
299 |
300 |
319 |
320 |
323 |
324 |
352 |
353 |
373 |
374 |
378 |
379 |
391 |
392 |
397 |
398 |
404 |
405 |
406 |
--------------------------------------------------------------------------------