diff --git a/api/Avalonia.Android.nupkg.xml b/api/Avalonia.Android.nupkg.xml
new file mode 100644
index 0000000000..da33e03f2c
--- /dev/null
+++ b/api/Avalonia.Android.nupkg.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ CP0002
+ M:Avalonia.Android.AndroidViewControlHandle.get_HandleDescriptor
+ baseline/net8.0-android34.0/Avalonia.Android.dll
+ target/net8.0-android34.0/Avalonia.Android.dll
+
+
+ CP0007
+ T:Avalonia.Android.AndroidViewControlHandle
+ baseline/net8.0-android34.0/Avalonia.Android.dll
+ target/net8.0-android34.0/Avalonia.Android.dll
+
+
\ No newline at end of file
diff --git a/api/Avalonia.Browser.nupkg.xml b/api/Avalonia.Browser.nupkg.xml
new file mode 100644
index 0000000000..0fb414ed14
--- /dev/null
+++ b/api/Avalonia.Browser.nupkg.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ CP0002
+ M:Avalonia.Browser.JSObjectControlHandle.get_Handle
+ baseline/net8.0-browser1.0/Avalonia.Browser.dll
+ target/net8.0-browser1.0/Avalonia.Browser.dll
+
+
+ CP0002
+ M:Avalonia.Browser.JSObjectControlHandle.get_HandleDescriptor
+ baseline/net8.0-browser1.0/Avalonia.Browser.dll
+ target/net8.0-browser1.0/Avalonia.Browser.dll
+
+
+ CP0007
+ T:Avalonia.Browser.JSObjectControlHandle
+ baseline/net8.0-browser1.0/Avalonia.Browser.dll
+ target/net8.0-browser1.0/Avalonia.Browser.dll
+
+
\ No newline at end of file
diff --git a/api/Avalonia.iOS.nupkg.xml b/api/Avalonia.iOS.nupkg.xml
new file mode 100644
index 0000000000..5f6e822d81
--- /dev/null
+++ b/api/Avalonia.iOS.nupkg.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ CP0002
+ M:Avalonia.iOS.UIViewControlHandle.get_HandleDescriptor
+ baseline/net8.0-tvos17.0/Avalonia.iOS.dll
+ target/net8.0-tvos17.0/Avalonia.iOS.dll
+
+
+ CP0007
+ T:Avalonia.iOS.UIViewControlHandle
+ baseline/net8.0-tvos17.0/Avalonia.iOS.dll
+ target/net8.0-tvos17.0/Avalonia.iOS.dll
+
+
\ No newline at end of file
diff --git a/src/Android/Avalonia.Android/AndroidViewControlHandle.cs b/src/Android/Avalonia.Android/AndroidViewControlHandle.cs
index 6d14ea787f..b2ccc6ff6e 100644
--- a/src/Android/Avalonia.Android/AndroidViewControlHandle.cs
+++ b/src/Android/Avalonia.Android/AndroidViewControlHandle.cs
@@ -1,5 +1,5 @@
using System;
-
+using Android.Runtime;
using Android.Views;
using Avalonia.Controls.Platform;
@@ -7,20 +7,16 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- public class AndroidViewControlHandle : INativeControlHostDestroyableControlHandle
+ public class AndroidViewControlHandle : PlatformHandle, INativeControlHostDestroyableControlHandle
{
- internal const string AndroidDescriptor = "JavaObjectHandle";
+ internal static string AndroidViewDescriptor = "android.view.View";
- public AndroidViewControlHandle(View view)
+ public AndroidViewControlHandle(View view) : base(view.Handle, AndroidViewDescriptor)
{
View = view;
}
- public View View { get; }
-
- public string HandleDescriptor => AndroidDescriptor;
-
- IntPtr IPlatformHandle.Handle => View.Handle;
+ public View View { get; private set; }
public void Destroy()
{
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index b5a4287fb0..cb03d85fc2 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -121,6 +121,7 @@ namespace Avalonia.Android
var settings =
AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(context);
+ ((AndroidScreens)_view.TryGetFeature()!).OnChanged();
}
}
diff --git a/src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs b/src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
index 7cc1e70cfd..9edd207627 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform
};
}
- public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidDescriptor;
+ public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidViewDescriptor;
private class AndroidNativeControlAttachment : INativeControlHostControlTopLevelAttachment
{
diff --git a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs
new file mode 100644
index 0000000000..667ec260fa
--- /dev/null
+++ b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using Android.Content;
+using Android.Hardware.Display;
+using Android.Runtime;
+using Android.Util;
+using Android.Views;
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Platform;
+using AndroidOrientation = global::Android.Content.Res.Orientation;
+
+namespace Avalonia.Android.Platform;
+
+internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandle(new IntPtr(display.DisplayId), "DisplayId"))
+{
+ public void Refresh(Context context)
+ {
+ DisplayName = display.Name;
+
+ var naturalOrientation = ScreenOrientation.Portrait;
+ var rotation = display.Rotation;
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(30)
+ && display.DisplayId == context.Display?.DisplayId
+ && context.Resources?.DisplayMetrics is { } primaryMetrics)
+ {
+ IsPrimary = true;
+ Scaling = primaryMetrics.Density;
+ Bounds = WorkingArea = new(0, 0, primaryMetrics.WidthPixels, primaryMetrics.HeightPixels);
+
+ var orientation = context.Resources.Configuration?.Orientation;
+ if (orientation == AndroidOrientation.Square)
+ naturalOrientation = ScreenOrientation.None;
+ else if (rotation is SurfaceOrientation.Rotation0 or SurfaceOrientation.Rotation180)
+ naturalOrientation = orientation == AndroidOrientation.Landscape ?
+ ScreenOrientation.Landscape :
+ ScreenOrientation.Portrait;
+ else
+ naturalOrientation = orientation == AndroidOrientation.Portrait ?
+ ScreenOrientation.Landscape :
+ ScreenOrientation.Portrait;
+ }
+ else
+ {
+ IsPrimary = false;
+ // These Display methods are deprecated since 31 SDK,
+ // But Android doesn't have any replacement, except for the primary screen.
+#pragma warning disable CS0618 // Type or member is obsolete
+#pragma warning disable CA1422 // Validate platform compatibility
+#pragma warning disable CA1416 // Validate platform compatibility
+ var displayMetrics = new DisplayMetrics();
+ display.GetRealMetrics(displayMetrics);
+#pragma warning restore CA1416 // Validate platform compatibility
+#pragma warning restore CA1422 // Validate platform compatibility
+#pragma warning restore CS0618 // Type or member is obsolete
+ Scaling = displayMetrics.Density;
+ Bounds = WorkingArea = new(0, 0, displayMetrics.WidthPixels, displayMetrics.HeightPixels);
+ }
+
+ CurrentOrientation = (display.Rotation, naturalOrientation) switch
+ {
+ (_, ScreenOrientation.None) => ScreenOrientation.None,
+ (SurfaceOrientation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape,
+ (SurfaceOrientation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait,
+ (SurfaceOrientation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped,
+ (SurfaceOrientation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped,
+ (SurfaceOrientation.Rotation0, _) => ScreenOrientation.Portrait,
+ (SurfaceOrientation.Rotation90, _) => ScreenOrientation.Landscape,
+ (SurfaceOrientation.Rotation180, _) => ScreenOrientation.PortraitFlipped,
+ (SurfaceOrientation.Rotation270, _) => ScreenOrientation.LandscapeFlipped,
+ _ => ScreenOrientation.Portrait
+ };
+ }
+}
+
+internal sealed class AndroidScreens : ScreensBase, IDisposable
+{
+ private readonly Context _context;
+ private readonly DisplayManager? _displayService;
+ private readonly DisplayListener? _listener;
+
+ public AndroidScreens(Context context) : base(new DisplayComparer())
+ {
+ _context = context;
+ _displayService = context.GetSystemService(Context.DisplayService).JavaCast();
+ if (_displayService is not null)
+ {
+ _listener = new DisplayListener(this);
+ _displayService.RegisterDisplayListener(_listener, null);
+ }
+ }
+
+ protected override IReadOnlyList GetAllScreenKeys()
+ {
+ if (_displayService?.GetDisplays() is { } displays)
+ {
+ return displays;
+ }
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(30) && _context.Display is { } defaultDisplay)
+ {
+ return [defaultDisplay];
+ }
+
+ return Array.Empty();
+ }
+
+ protected override AndroidScreen CreateScreenFromKey(Display display) => new(display);
+
+ protected override void ScreenChanged(AndroidScreen screen) => screen.Refresh(_context);
+
+ protected override Screen? ScreenFromTopLevelCore(ITopLevelImpl topLevel)
+ {
+ var display = ((TopLevelImpl)topLevel).View.Display;
+ return display is not null && TryGetScreen(display, out var screen) ? screen : null;
+ }
+
+ protected override Screen? ScreenFromPointCore(PixelPoint point) => null;
+ protected override Screen? ScreenFromRectCore(PixelRect rect) => null;
+
+ public void Dispose()
+ {
+ _displayService?.UnregisterDisplayListener(_listener);
+ _displayService?.Dispose();
+ _listener?.Dispose();
+ }
+
+ private class DisplayListener(AndroidScreens screens) : Java.Lang.Object, DisplayManager.IDisplayListener
+ {
+ public void OnDisplayAdded(int displayId) => screens.OnChanged();
+ public void OnDisplayChanged(int displayId) => screens.OnChanged();
+ public void OnDisplayRemoved(int displayId) => screens.OnChanged();
+ }
+
+ private class DisplayComparer : IEqualityComparer
+ {
+ public bool Equals(Display? x, Display? y) => x?.DisplayId == y?.DisplayId;
+ public int GetHashCode(Display obj) => obj.DisplayId;
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 8b0d80a416..2fb8c5047c 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -44,6 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly AndroidInsetsManager? _insetsManager;
private readonly ClipboardImpl _clipboard;
private readonly AndroidLauncher? _launcher;
+ private readonly AndroidScreens? _screens;
private ViewImpl _view;
private WindowTransparencyLevel _transparencyLevel;
@@ -61,6 +62,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this);
_clipboard = new ClipboardImpl(avaloniaView.Context.GetSystemService(Context.ClipboardService).JavaCast());
+ _screens = new AndroidScreens(avaloniaView.Context);
RenderScaling = _view.Scaling;
@@ -399,6 +401,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return _launcher;
}
+ if (featureType == typeof(IScreenImpl))
+ {
+ return _screens;
+ }
+
return null;
}
diff --git a/src/Browser/Avalonia.Browser/BrowserScreens.cs b/src/Browser/Avalonia.Browser/BrowserScreens.cs
new file mode 100644
index 0000000000..9f0b22525b
--- /dev/null
+++ b/src/Browser/Avalonia.Browser/BrowserScreens.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading.Tasks;
+using Avalonia.Browser.Interop;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using BrowserScreenHelper = Avalonia.Browser.Interop.ScreenHelper;
+
+namespace Avalonia.Browser;
+
+internal sealed class BrowserScreen(JSObject screen) : PlatformScreen(new JSObjectPlatformHandle(screen))
+{
+ internal bool IsCurrent { get; set; }
+
+ public void Refresh()
+ {
+ IsCurrent = BrowserScreenHelper.IsCurrent(screen);
+ DisplayName = BrowserScreenHelper.GetDisplayName(screen);
+ Scaling = BrowserScreenHelper.GetScaling(screen);
+ IsPrimary = BrowserScreenHelper.IsPrimary(screen);
+ CurrentOrientation = (ScreenOrientation)BrowserScreenHelper.GetCurrentOrientation(screen);
+ Bounds = BrowserScreenHelper.GetBounds(screen) is { } boundsArr ?
+ new PixelRect((int)boundsArr[0], (int)boundsArr[1], (int)boundsArr[2], (int)boundsArr[3]) :
+ new PixelRect();
+ WorkingArea = BrowserScreenHelper.GetWorkingArea(screen) is { } workingAreaArr ?
+ new PixelRect((int)workingAreaArr[0], (int)workingAreaArr[1], (int)workingAreaArr[2],
+ (int)workingAreaArr[3]) :
+ new PixelRect();
+ }
+}
+
+internal sealed class BrowserScreens : ScreensBase
+{
+ private bool _isExtended;
+
+ public BrowserScreens()
+ {
+ BrowserScreenHelper.SubscribeOnChanged(BrowserWindowingPlatform.GlobalThis);
+ BrowserScreenHelper.CheckPermissions(BrowserWindowingPlatform.GlobalThis);
+ }
+
+ protected override IReadOnlyList GetAllScreenKeys() =>
+ BrowserScreenHelper.GetAllScreens(BrowserWindowingPlatform.GlobalThis);
+
+ protected override BrowserScreen CreateScreenFromKey(JSObject key) => new(key);
+ protected override void ScreenChanged(BrowserScreen screen) => screen.Refresh();
+
+ protected override Screen? ScreenFromTopLevelCore(ITopLevelImpl topLevel) =>
+ AllScreens.OfType().FirstOrDefault(s => s.IsCurrent);
+
+ protected override Screen? ScreenFromPointCore(PixelPoint point) =>
+ _isExtended ? base.ScreenFromPointCore(point) : null;
+
+ protected override Screen? ScreenFromRectCore(PixelRect rect) => _isExtended ? base.ScreenFromRectCore(rect) : null;
+
+ protected override async Task RequestScreenDetailsCore()
+ {
+ try
+ {
+ return _isExtended = await BrowserScreenHelper.RequestDetailedScreens(BrowserWindowingPlatform.GlobalThis);
+ }
+ catch (Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.BrowserPlatform)?
+ .Log(this, "Failed to get extended screen details: {Exception}", e);
+ return false;
+ }
+ }
+}
diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
index 9c15d8d1a5..df010953d9 100644
--- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
+++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
@@ -165,6 +165,11 @@ namespace Avalonia.Browser
return AvaloniaLocator.Current.GetService();
}
+ if (featureType == typeof(IScreenImpl))
+ {
+ return AvaloniaLocator.Current.GetService();
+ }
+
if (featureType == typeof(INativeControlHostImpl))
{
return _nativeControlHost;
diff --git a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs
index 6817e6a6da..4b929660e5 100644
--- a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs
+++ b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs
@@ -50,4 +50,11 @@ internal static partial class DomHelper
(AvaloniaLocator.Current.GetService() as BrowserActivatableLifetime)?.OnVisibilityStateChanged(visibilityState);
return Task.CompletedTask;
}
+
+ [JSExport]
+ public static Task ScreensChanged()
+ {
+ (AvaloniaLocator.Current.GetService() as BrowserScreens)?.OnChanged();
+ return Task.CompletedTask;
+ }
}
diff --git a/src/Browser/Avalonia.Browser/Interop/ScreenHelper.cs b/src/Browser/Avalonia.Browser/Interop/ScreenHelper.cs
new file mode 100644
index 0000000000..90b1dcfafd
--- /dev/null
+++ b/src/Browser/Avalonia.Browser/Interop/ScreenHelper.cs
@@ -0,0 +1,41 @@
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading.Tasks;
+
+namespace Avalonia.Browser.Interop;
+
+internal static partial class ScreenHelper
+{
+ [JSImport("ScreenHelper.subscribeOnChanged", AvaloniaModule.MainModuleName)]
+ public static partial void SubscribeOnChanged(JSObject globalThis);
+
+ [JSImport("ScreenHelper.checkPermissions", AvaloniaModule.MainModuleName)]
+ public static partial void CheckPermissions(JSObject globalThis);
+
+ [JSImport("ScreenHelper.getAllScreens", AvaloniaModule.MainModuleName)]
+ public static partial JSObject[] GetAllScreens(JSObject globalThis);
+
+ [JSImport("ScreenHelper.requestDetailedScreens", AvaloniaModule.MainModuleName)]
+ [return: JSMarshalAs>]
+ public static partial Task RequestDetailedScreens(JSObject globalThis);
+
+ [JSImport("ScreenHelper.getDisplayName", AvaloniaModule.MainModuleName)]
+ public static partial string GetDisplayName(JSObject screen);
+
+ [JSImport("ScreenHelper.getScaling", AvaloniaModule.MainModuleName)]
+ public static partial double GetScaling(JSObject screen);
+
+ [JSImport("ScreenHelper.getBounds", AvaloniaModule.MainModuleName)]
+ public static partial double[] GetBounds(JSObject screen);
+
+ [JSImport("ScreenHelper.getWorkingArea", AvaloniaModule.MainModuleName)]
+ public static partial double[] GetWorkingArea(JSObject screen);
+
+ [JSImport("ScreenHelper.isCurrent", AvaloniaModule.MainModuleName)]
+ public static partial bool IsCurrent(JSObject screen);
+
+ [JSImport("ScreenHelper.isPrimary", AvaloniaModule.MainModuleName)]
+ public static partial bool IsPrimary(JSObject screen);
+
+ [JSImport("ScreenHelper.getCurrentOrientation", AvaloniaModule.MainModuleName)]
+ public static partial int GetCurrentOrientation(JSObject screen);
+}
diff --git a/src/Browser/Avalonia.Browser/JSObjectControlHandle.cs b/src/Browser/Avalonia.Browser/JSObjectControlHandle.cs
index b0c8cecca6..72b908efe4 100644
--- a/src/Browser/Avalonia.Browser/JSObjectControlHandle.cs
+++ b/src/Browser/Avalonia.Browser/JSObjectControlHandle.cs
@@ -1,28 +1,34 @@
using System;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;
using Avalonia.Controls.Platform;
+using Avalonia.Platform;
namespace Avalonia.Browser;
-public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle
+public class JSObjectPlatformHandle : PlatformHandle
{
internal const string ElementReferenceDescriptor = "JSObject";
- public JSObjectControlHandle(JSObject reference)
+ // GetHashCode returns internal JSHandle.
+ internal JSObjectPlatformHandle(JSObject reference) : base(reference.GetHashCode(), ElementReferenceDescriptor)
{
Object = reference;
}
public JSObject Object { get; }
+}
- public IntPtr Handle => throw new NotSupportedException();
-
- public string? HandleDescriptor => ElementReferenceDescriptor;
+public class JSObjectControlHandle : JSObjectPlatformHandle, INativeControlHostDestroyableControlHandle
+{
+ public JSObjectControlHandle(JSObject reference) : base(reference)
+ {
+ }
public void Destroy()
{
- if (Object is JSObject inProcess && !inProcess.IsDisposed)
+ if (Object is { } inProcess && !inProcess.IsDisposed)
{
inProcess.Dispose();
}
diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
index bbb1455383..0589b67c0d 100644
--- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs
+++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
@@ -88,6 +88,7 @@ internal class BrowserWindowingPlatform : IWindowingPlatform
.Bind().ToConstant(s_keyboard)
.Bind().ToSingleton()
.Bind().ToSingleton()
+ .Bind().ToSingleton()
.Bind().ToConstant(instance)
.Bind().ToSingleton()
.Bind().ToSingleton()
diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts
index c663b4c1b9..1b2393f715 100644
--- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts
+++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts
@@ -11,6 +11,7 @@ import { WebRenderTargetRegistry } from "./avalonia/rendering/webRenderTargetReg
import { WebRenderTarget } from "./avalonia/rendering/webRenderTarget";
import { SoftwareRenderTarget } from "./avalonia/rendering/softwareRenderTarget";
import { WebGlRenderTarget } from "./avalonia/rendering/webGlRenderTarget";
+import { ScreenHelper } from "./avalonia/screens";
async function registerServiceWorker(path: string, scope: string | undefined) {
if ("serviceWorker" in navigator) {
@@ -26,6 +27,7 @@ export {
NativeControlHost,
NavigationHelper,
GeneralHelpers,
+ ScreenHelper,
TimerHelper,
WebRenderTarget,
CanvasSurface,
diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/screens.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/screens.ts
new file mode 100644
index 0000000000..11ee99dc3c
--- /dev/null
+++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/screens.ts
@@ -0,0 +1,138 @@
+import { JsExports } from "./jsExports";
+
+type SingleScreen = Screen & { window: Window; availLeft: number; availTop: number };
+type ScreenDetailedEx = ScreenDetailed & { availLeft: number; availTop: number };
+type BrowserScreen = ScreenDetailedEx | SingleScreen;
+enum ScreenOrientation {
+ None,
+ Landscape = 1,
+ Portrait = 2,
+ LandscapeFlipped = 4,
+ PortraitFlipped = 8
+}
+
+export class ScreenHelper {
+ static detailedScreens?: ScreenDetails;
+
+ private static raiseOnChanged() {
+ JsExports.DomHelper.ScreensChanged();
+ }
+
+ public static async checkPermissions(globalThis: Window): Promise {
+ // If previous session already granted "window-management" permissions, just re-request details, before they are used.
+ const { state } = await globalThis.navigator.permissions.query({ name: "window-management" } as any);
+ if (state === "granted") {
+ await this.requestDetailedScreens(globalThis);
+ }
+ }
+
+ public static subscribeOnChanged(globalThis: Window) {
+ if (this.detailedScreens) {
+ globalThis.screen.removeEventListener("change", this.raiseOnChanged);
+ this.detailedScreens.addEventListener("screenschange", this.raiseOnChanged);
+
+ // When any screen was added, we re-subscribe on all of them to keep it simpler.
+ // Just like in C#, it's safer to re-subscribe if handler is the same function - it will trigger it once.
+ for (const screen of this.detailedScreens.screens) {
+ screen.addEventListener("change", this.raiseOnChanged);
+ }
+ } else {
+ globalThis.screen.addEventListener("change", this.raiseOnChanged);
+ }
+ }
+
+ public static getAllScreens(globalThis: Window): BrowserScreen[] {
+ if (this.detailedScreens) {
+ return this.detailedScreens.screens as ScreenDetailedEx[];
+ } else {
+ const singleScreen = Object.assign(globalThis.screen, { window: globalThis }) as SingleScreen;
+ return [singleScreen];
+ }
+ }
+
+ public static async requestDetailedScreens(globalThis: Window): Promise {
+ if (this.detailedScreens) {
+ return true;
+ }
+ if ("getScreenDetails" in globalThis) {
+ this.detailedScreens = await globalThis.getScreenDetails();
+ if (this.detailedScreens) {
+ this.subscribeOnChanged(globalThis);
+ globalThis.setTimeout(this.raiseOnChanged, 1);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static getDisplayName(screen: BrowserScreen) {
+ return (screen as ScreenDetailed)?.label;
+ }
+
+ static getScaling(screen: BrowserScreen) {
+ if ("devicePixelRatio" in screen) {
+ return screen.devicePixelRatio;
+ }
+ if ("window" in screen) {
+ return screen.window.devicePixelRatio;
+ }
+ return 1;
+ }
+
+ static getBounds(screen: BrowserScreen): number[] {
+ const width = screen.width;
+ const height = screen.height;
+
+ if ("left" in screen && "top" in screen) {
+ return [screen.left, screen.top, width, height];
+ } else if ("availLeft" in screen && "availTop" in screen) {
+ // If webapp doesn't have "window-management" perms, "left" and "top" will be undefined.
+ // To keep getBounds consistent getWorkingArea, while still usable, fallback to availLeft and availTop values.
+ return [screen.availLeft, screen.availTop, width, height];
+ }
+
+ return [0, 0, width, height];
+ }
+
+ static getWorkingArea(screen: BrowserScreen): number[] {
+ const width = screen.availWidth;
+ const height = screen.availHeight;
+
+ if ("availLeft" in screen && "availTop" in screen) {
+ return [screen.availLeft, screen.availTop, width, height];
+ }
+ return [0, 0, width, height];
+ }
+
+ static isCurrent(screen: BrowserScreen): boolean {
+ if (this.detailedScreens) {
+ return this.detailedScreens.currentScreen === screen;
+ }
+
+ // If detailedScreens were not requested - we have a single screen which always is a current one.
+ return true;
+ }
+
+ static isPrimary(screen: BrowserScreen): boolean {
+ if ("isPrimary" in screen) {
+ return screen.isPrimary;
+ }
+
+ // If detailedScreens were not requested - we have a single screen which always is a current one, and we assume it's a primary one as well.
+ return true;
+ }
+
+ /* eslint-disable indent */
+ static getCurrentOrientation(screen: BrowserScreen): ScreenOrientation {
+ switch (screen.orientation.type) {
+ case "landscape-primary":
+ return ScreenOrientation.Landscape;
+ case "landscape-secondary":
+ return ScreenOrientation.LandscapeFlipped;
+ case "portrait-primary":
+ return ScreenOrientation.Portrait;
+ case "portrait-secondary":
+ return ScreenOrientation.PortraitFlipped;
+ }
+ }
+}
diff --git a/src/Browser/Avalonia.Browser/webapp/package-lock.json b/src/Browser/Avalonia.Browser/webapp/package-lock.json
index 12aa6660d8..51d328418e 100644
--- a/src/Browser/Avalonia.Browser/webapp/package-lock.json
+++ b/src/Browser/Avalonia.Browser/webapp/package-lock.json
@@ -11,6 +11,7 @@
"devDependencies": {
"@types/emscripten": "^1.39.6",
"@types/offscreencanvas": "2019.7.1",
+ "@types/webscreens-window-placement": "^0.1.3",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"esbuild": "^0.15.7",
"eslint": "^8.24.0",
@@ -179,6 +180,12 @@
"integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==",
"dev": true
},
+ "node_modules/@types/webscreens-window-placement": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@types/webscreens-window-placement/-/webscreens-window-placement-0.1.3.tgz",
+ "integrity": "sha512-OunHLGJkmAuNlvd7PrRbQy/VleLyxxP3NKwuUo9OS412vO/tzKGwW2K3FqvnM1yebTkCM0W+gszr08m9oDz0lg==",
+ "dev": true
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.38.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz",
@@ -493,12 +500,12 @@
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -1612,9 +1619,9 @@
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -2248,18 +2255,6 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
- "node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@@ -2380,9 +2375,9 @@
}
},
"node_modules/normalize-package-data/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
@@ -2498,9 +2493,9 @@
}
},
"node_modules/npm-run-all/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
@@ -2942,13 +2937,10 @@
}
},
"node_modules/semver": {
- "version": "7.3.7",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
- "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
"bin": {
"semver": "bin/semver.js"
},
@@ -3317,12 +3309,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -3447,6 +3433,12 @@
"integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==",
"dev": true
},
+ "@types/webscreens-window-placement": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@types/webscreens-window-placement/-/webscreens-window-placement-0.1.3.tgz",
+ "integrity": "sha512-OunHLGJkmAuNlvd7PrRbQy/VleLyxxP3NKwuUo9OS412vO/tzKGwW2K3FqvnM1yebTkCM0W+gszr08m9oDz0lg==",
+ "dev": true
+ },
"@typescript-eslint/eslint-plugin": {
"version": "5.38.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz",
@@ -3636,12 +3628,12 @@
}
},
"braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
}
},
"builtins": {
@@ -4370,9 +4362,9 @@
}
},
"fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
@@ -4829,15 +4821,6 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
- "lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "requires": {
- "yallist": "^4.0.0"
- }
- },
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@@ -4919,9 +4902,9 @@
},
"dependencies": {
"semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true
}
}
@@ -5010,9 +4993,9 @@
"dev": true
},
"semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true
},
"shebang-command": {
@@ -5303,13 +5286,10 @@
}
},
"semver": {
- "version": "7.3.7",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
- "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
- "dev": true,
- "requires": {
- "lru-cache": "^6.0.0"
- }
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "dev": true
},
"shebang-command": {
"version": "2.0.0",
@@ -5584,12 +5564,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/src/Browser/Avalonia.Browser/webapp/package.json b/src/Browser/Avalonia.Browser/webapp/package.json
index 065399492e..8df7cb27ed 100644
--- a/src/Browser/Avalonia.Browser/webapp/package.json
+++ b/src/Browser/Avalonia.Browser/webapp/package.json
@@ -10,6 +10,7 @@
"@types/emscripten": "^1.39.6",
"@types/offscreencanvas": "2019.7.1",
"@typescript-eslint/eslint-plugin": "^5.38.1",
+ "@types/webscreens-window-placement": "^0.1.3",
"esbuild": "^0.15.7",
"eslint": "^8.24.0",
"eslint-config-standard-with-typescript": "^23.0.0",
diff --git a/src/Tizen/Avalonia.Tizen/NuiScreens.cs b/src/Tizen/Avalonia.Tizen/NuiScreens.cs
new file mode 100644
index 0000000000..0c52ffbcf9
--- /dev/null
+++ b/src/Tizen/Avalonia.Tizen/NuiScreens.cs
@@ -0,0 +1,73 @@
+using Avalonia.Platform;
+using Tizen.Applications;
+using Tizen.Multimedia;
+using Tizen.NUI;
+using Tizen.System;
+
+namespace Avalonia.Tizen;
+
+internal class NuiScreens : ScreensBase
+{
+ // See https://github.com/dotnet/maui/blob/8.0.70/src/Essentials/src/DeviceDisplay/DeviceDisplay.tizen.cs
+ internal const float BaseLogicalDpi = 160.0f;
+
+ internal static DeviceOrientation LastDeviceOrientation { get; private set; }
+
+ internal static int DisplayWidth =>
+ Information.TryGetValue("http://tizen.org/feature/screen.width", out var value) ? value : 0;
+
+ internal static int DisplayHeight =>
+ Information.TryGetValue("http://tizen.org/feature/screen.height", out var value) ? value : 0;
+
+ internal static int DisplayDpi => TizenRuntimePlatform.Info.Value.IsTV ? 72 :
+ Information.TryGetValue("http://tizen.org/feature/screen.dpi", out var value) ? value : 72;
+
+ public NuiScreens()
+ {
+ ((CoreApplication)global::Tizen.Applications.Application.Current).DeviceOrientationChanged += (sender, args) =>
+ {
+ LastDeviceOrientation = args.DeviceOrientation;
+ OnChanged();
+ };
+ }
+
+ protected override int GetScreenCount() => 1;
+
+ protected override IReadOnlyList GetAllScreenKeys() => [1];
+
+ protected override SingleTizenScreen CreateScreenFromKey(int key)
+ {
+ var screen = new SingleTizenScreen(key);
+ screen.Refresh();
+ return screen;
+ }
+
+ protected override void ScreenChanged(SingleTizenScreen screen) => screen.Refresh();
+}
+
+internal class SingleTizenScreen(int index) : PlatformScreen(new PlatformHandle(new IntPtr(index), nameof(SingleTizenScreen)))
+{
+ public void Refresh()
+ {
+ IsPrimary = index == 1;
+ if (IsPrimary)
+ {
+ Bounds = WorkingArea = new PixelRect(0, 0, NuiScreens.DisplayWidth, NuiScreens.DisplayHeight);
+ Scaling = NuiScreens.DisplayDpi / NuiScreens.BaseLogicalDpi;
+
+ var isNaturalLandscape = Bounds.Width > Bounds.Height;
+ CurrentOrientation = (isNaturalLandscape, NuiScreens.LastDeviceOrientation) switch
+ {
+ (true, DeviceOrientation.Orientation_0) => ScreenOrientation.Landscape,
+ (true, DeviceOrientation.Orientation_90) => ScreenOrientation.Portrait,
+ (true, DeviceOrientation.Orientation_180) => ScreenOrientation.LandscapeFlipped,
+ (true, DeviceOrientation.Orientation_270) => ScreenOrientation.PortraitFlipped,
+ (false, DeviceOrientation.Orientation_0) => ScreenOrientation.Portrait,
+ (false, DeviceOrientation.Orientation_90) => ScreenOrientation.Landscape,
+ (false, DeviceOrientation.Orientation_180) => ScreenOrientation.PortraitFlipped,
+ (false, DeviceOrientation.Orientation_270) => ScreenOrientation.LandscapeFlipped,
+ _ => ScreenOrientation.None
+ };
+ }
+ }
+}
diff --git a/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs b/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs
index b28db8dff8..f806a03d95 100644
--- a/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs
+++ b/src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs
@@ -22,7 +22,7 @@ internal static class TizenRuntimePlatformServices
internal class TizenRuntimePlatform : StandardRuntimePlatform
{
- private static readonly Lazy Info = new(() =>
+ public static readonly Lazy Info = new(() =>
{
global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile);
diff --git a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs
index 862d0d7e78..b7c3490622 100644
--- a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs
+++ b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs
@@ -16,6 +16,7 @@ internal class TopLevelImpl : ITopLevelImpl
private readonly ITizenView _view;
private readonly NuiClipboardImpl _clipboard;
private readonly IStorageProvider _storageProvider;
+ private readonly NuiScreens _screen;
public TopLevelImpl(ITizenView view, IEnumerable