File: DeviceDisplay\DeviceDisplay.uwp.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
#nullable enable
using System;
using System.Runtime.InteropServices;
using Microsoft.Maui.ApplicationModel;
using Microsoft.UI.Windowing;
using Windows.Graphics.Display;
using Windows.System.Display;
 
namespace Microsoft.Maui.Devices
{
	partial class DeviceDisplayImplementation
	{
		readonly object locker = new object();
		readonly ActiveWindowTracker _activeWindowTracker;
 
		DisplayRequest? displayRequest;
 
		/// <summary>
		/// Initializes a new instance of the <see cref="DeviceDisplayImplementation"/> class.
		/// </summary>
		public DeviceDisplayImplementation()
		{
			_activeWindowTracker = new(WindowStateManager.Default);
			_activeWindowTracker.WindowMessage += OnWindowMessage;
		}
 
		protected override bool GetKeepScreenOn()
		{
			lock (locker)
			{
				return displayRequest != null;
			}
		}
 
		protected override void SetKeepScreenOn(bool keepScreenOn)
		{
			lock (locker)
			{
				if (keepScreenOn)
				{
					if (displayRequest == null)
					{
						displayRequest = new DisplayRequest();
						displayRequest.RequestActive();
					}
				}
				else
				{
					if (displayRequest != null)
					{
						displayRequest.RequestRelease();
						displayRequest = null;
					}
				}
			}
		}
 
		protected override DisplayInfo GetMainDisplayInfo()
		{
			if (WindowStateManager.Default.GetActiveAppWindow(false) is not AppWindow appWindow)
				return default;
 
			var windowHandle = UI.Win32Interop.GetWindowFromWindowId(appWindow.Id);
			var mi = GetDisplay(windowHandle);
 
			if (mi == null)
				return default;
 
			var vDevMode = new DEVMODE();
			EnumDisplaySettings(mi.Value.DeviceNameToLPTStr(), -1, ref vDevMode);
 
			var w = vDevMode.dmPelsWidth;
			var h = vDevMode.dmPelsHeight;
 
			var dpi = (double)GetDpiForWindow(windowHandle);
			if (dpi != 0)
				dpi /= DeviceDisplay.BaseLogicalDpi;
			else
				dpi = 1.0;
 
			var displayOrientation = GetDisplayOrientation(vDevMode);
			var rotation = CalculateRotation(displayOrientation);
 
			var orientation = displayOrientation == DisplayOrientations.Landscape || displayOrientation == DisplayOrientations.LandscapeFlipped
				? DisplayOrientation.Landscape
				: DisplayOrientation.Portrait;
 
			return new DisplayInfo(
				width: w,
				height: h,
				density: dpi,
				orientation: orientation,
				rotation: rotation,
				rate: vDevMode.dmDisplayFrequency);
		}
 
		static MONITORINFOEX? GetDisplay(IntPtr hwnd)
		{
			IntPtr hMonitor;
			RECT rc;
			GetWindowRect(hwnd, out rc);
			hMonitor = MonitorFromRect(ref rc, MonitorOptions.MONITOR_DEFAULTTONEAREST);
 
			MONITORINFOEX mi = new MONITORINFOEX();
			mi.Size = Marshal.SizeOf(mi);
			bool success = GetMonitorInfo(hMonitor, ref mi);
			if (success)
			{
				return mi;
			}
			return null;
		}
 
		protected override void StartScreenMetricsListeners() =>
			MainThread.BeginInvokeOnMainThread(_activeWindowTracker.Start);
 
		protected override void StopScreenMetricsListeners() =>
			MainThread.BeginInvokeOnMainThread(_activeWindowTracker.Stop);
 
		// Currently there isn't a way to detect Orientation Changes unless you subclass the WinUI.Window and watch the messages.
		// This is the "subtlest" way to currently wire this together.
		// Hopefully there will be a more public API for this down the road so we can just use that directly from Essentials
		void OnWindowMessage(object? sender, WindowMessageEventArgs e)
		{
			if (e.MessageId == PlatformMethods.MessageIds.WM_DISPLAYCHANGE ||
				e.MessageId == PlatformMethods.MessageIds.WM_DPICHANGED)
				OnMainDisplayInfoChanged();
		}
 
		static DisplayRotation CalculateRotation(DisplayOrientations orientation) =>
			orientation switch
			{
				DisplayOrientations.Landscape => DisplayRotation.Rotation0,
				DisplayOrientations.Portrait => DisplayRotation.Rotation270,
				DisplayOrientations.LandscapeFlipped => DisplayRotation.Rotation180,
				DisplayOrientations.PortraitFlipped => DisplayRotation.Rotation90,
				_ => DisplayRotation.Rotation0,
			};
 
		static DisplayOrientations GetDisplayOrientation(DEVMODE devMode) =>
			devMode.dmDisplayOrientation switch
			{
				0 => DisplayOrientations.Landscape,
				1 => DisplayOrientations.Portrait,
				2 => DisplayOrientations.LandscapeFlipped,
				3 => DisplayOrientations.PortraitFlipped,
				_ => DisplayOrientations.Landscape,
			};
 
		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		static extern int GetDpiForWindow(IntPtr hwnd);
 
		[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		static extern IntPtr MonitorFromRect(ref RECT lprc, MonitorOptions dwFlags);
 
		[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
 
		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool EnumDisplaySettings(
			byte[] lpszDeviceName,
			[param: MarshalAs(UnmanagedType.U4)] int iModeNum,
			[In, Out] ref DEVMODE lpDevMode);
 
		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
 
		enum MonitorOptions : uint
		{
			MONITOR_DEFAULTTONULL,
			MONITOR_DEFAULTTOPRIMARY,
			MONITOR_DEFAULTTONEAREST
		}
 
		[StructLayout(LayoutKind.Sequential)]
		struct RECT
		{
			public int Left;
			public int Top;
			public int Right;
			public int Bottom;
		}
 
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
		struct MONITORINFOEX
		{
			public int Size;
			public RECT Monitor;
			public RECT WorkArea;
			public uint Flags;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
			public string DeviceName;
 
			public byte[] DeviceNameToLPTStr()
			{
				var lptArray = new byte[DeviceName.Length + 1];
 
				var index = 0;
				foreach (char c in DeviceName.ToCharArray())
					lptArray[index++] = Convert.ToByte(c);
 
				lptArray[index] = Convert.ToByte('\0');
 
				return lptArray;
			}
		}
 
		struct DEVMODE
		{
			const int CCHDEVICENAME = 0x20;
			const int CCHFORMNAME = 0x20;
 
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
			public string dmDeviceName;
			public short dmSpecVersion;
			public short dmDriverVersion;
			public short dmSize;
			public short dmDriverExtra;
			public int dmFields;
			public int dmPositionX;
			public int dmPositionY;
			public int dmDisplayOrientation;
			public int dmDisplayFixedOutput;
			public short dmColor;
			public short dmDuplex;
			public short dmYResolution;
			public short dmTTOption;
			public short dmCollate;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
			public string dmFormName;
			public short dmLogPixels;
			public int dmBitsPerPel;
			public int dmPelsWidth;
			public int dmPelsHeight;
			public int dmDisplayFlags;
			public int dmDisplayFrequency;
			public int dmICMMethod;
			public int dmICMIntent;
			public int dmMediaType;
			public int dmDitherType;
			public int dmReserved1;
			public int dmReserved2;
			public int dmPanningWidth;
			public int dmPanningHeight;
		}
	}
}