Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Essentials fix DeviceDisplay and DeviceInfo #3738

Merged
merged 5 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Core/src/Platform/Windows/MauiWinUIWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ IntPtr NewWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)

NativeMessage?.Invoke(this, args);

Essentials.Platform.NewWindowProc(hWnd, msg, wParam, lParam);
return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}

Expand Down
15 changes: 15 additions & 0 deletions src/Essentials/samples/Samples/Platforms/Windows/app.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="Microsoft.Maui.Essentials.Sample.app"/>

<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>
318 changes: 288 additions & 30 deletions src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#nullable enable
using System;
using System.Runtime.InteropServices;
using Microsoft.UI.Windowing;
using Windows.Graphics.Display;
using Windows.Graphics.Display.Core;
using Windows.System.Display;
Expand Down Expand Up @@ -47,6 +49,289 @@ public bool KeepScreenOn
}
}

static DisplayRotation CalculateRotation(DisplayOrientations native, DisplayOrientations current)
{
if (native == DisplayOrientations.Portrait)
{
switch (current)
{
case DisplayOrientations.Landscape:
return DisplayRotation.Rotation90;
case DisplayOrientations.Portrait:
return DisplayRotation.Rotation0;
case DisplayOrientations.LandscapeFlipped:
return DisplayRotation.Rotation270;
case DisplayOrientations.PortraitFlipped:
return DisplayRotation.Rotation180;
}
}
else if (native == DisplayOrientations.Landscape)
{
switch (current)
{
case DisplayOrientations.Landscape:
return DisplayRotation.Rotation0;
case DisplayOrientations.Portrait:
return DisplayRotation.Rotation270;
case DisplayOrientations.LandscapeFlipped:
return DisplayRotation.Rotation180;
case DisplayOrientations.PortraitFlipped:
return DisplayRotation.Rotation90;
}
}

return DisplayRotation.Unknown;
}

#if WINDOWS

AppWindow? _currentAppWindowListeningTo;
public DisplayInfo GetMainDisplayInfo()
{
var appWindow = Platform.CurrentAppWindow;
var windowHandler = Platform.CurrentWindowHandle;
var mi = GetDisplay(windowHandler);

if (mi == null)
return new DisplayInfo();

DEVMODE vDevMode = new DEVMODE();
EnumDisplaySettings(mi.Value.DeviceNameToLPTStr(), -1, ref vDevMode);

var rotation = CalculateRotation(vDevMode, appWindow);
var perpendicular =
rotation == DisplayRotation.Rotation90 ||
rotation == DisplayRotation.Rotation270;

var w = vDevMode.dmPelsWidth;
var h = vDevMode.dmPelsHeight;
var dpi = GetDpiForWindow(windowHandler) / 96.0;

return new DisplayInfo(
width: perpendicular ? h : w,
height: perpendicular ? w : h,
density: dpi,
orientation: GetWindowOrientationWin32(appWindow) == DisplayOrientations.Landscape ? DisplayOrientation.Landscape : DisplayOrientation.Portrait,
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;
}

public void StartScreenMetricsListeners()
{
MainThread.BeginInvokeOnMainThread(() =>
{
Platform.CurrentWindowDisplayChanged += OnWindowDisplayChanged;
Platform.CurrentWindowChanged += OnCurrentWindowChanged;
_currentAppWindowListeningTo = Platform.CurrentAppWindow;
_currentAppWindowListeningTo.Changed += OnAppWindowChanged;
});
}

public void StopScreenMetricsListeners()
{
MainThread.BeginInvokeOnMainThread(() =>
{
Platform.CurrentWindowChanged -= OnCurrentWindowChanged;
Platform.CurrentWindowDisplayChanged -= OnWindowDisplayChanged;

if (_currentAppWindowListeningTo != null)
_currentAppWindowListeningTo.Changed -= OnAppWindowChanged;

_currentAppWindowListeningTo = null;
});
}

void OnCurrentWindowChanged(object? sender, EventArgs e)
{
if (_currentAppWindowListeningTo != null)
{
_currentAppWindowListeningTo.Changed -= OnAppWindowChanged;
}

_currentAppWindowListeningTo = Platform.CurrentAppWindow;
_currentAppWindowListeningTo.Changed += OnAppWindowChanged;
}

void OnWindowDisplayChanged(object? sender, EventArgs e)
{
WindowDisplayPropertiesChanged();
}

void WindowDisplayPropertiesChanged()
{
var metrics = GetMainDisplayInfo();
MainDisplayInfoChanged?.Invoke(this, new DisplayInfoChangedEventArgs(metrics));
}

void OnAppWindowChanged(AppWindow sender, AppWindowChangedEventArgs args) =>
WindowDisplayPropertiesChanged();

static DisplayRotation CalculateRotation(DEVMODE devMode, AppWindow appWindow)
{
DisplayOrientations native = DisplayOrientations.Portrait;
switch (devMode.dmDisplayOrientation)
{
case 0:
native = DisplayOrientations.Landscape;
break;
case 1:
native = DisplayOrientations.Portrait;
break;
case 2:
native = DisplayOrientations.LandscapeFlipped;
break;
case 3:
native = DisplayOrientations.PortraitFlipped;
break;
}

var current = GetWindowOrientationWin32(appWindow);
return CalculateRotation(native, current);
}

// https://github.com/marb2000/DesktopWindow/blob/abb21b797767bb24da09c066514117d5f1aabd75/WindowExtensions/DesktopWindow.cs#L407
static DisplayOrientations GetWindowOrientationWin32(AppWindow appWindow)
{
DisplayOrientations orientationEnum;
int theScreenWidth = appWindow.Size.Width;
int theScreenHeight = appWindow.Size.Height;
if (theScreenWidth > theScreenHeight)
orientationEnum = DisplayOrientations.Landscape;
else
orientationEnum = DisplayOrientations.Portrait;

return orientationEnum;
}

[DllImport("User32", CharSet = CharSet.Unicode)]
static extern int GetDpiForWindow(IntPtr hwnd);

[DllImport("User32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr MonitorFromRect(ref RECT lprc, MonitorOptions dwFlags);

[DllImport("User32", 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 Boolean 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);

[DllImport("user32.dll")]
static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
{
MONITORINFOEX mi = new MONITORINFOEX();
mi.Size = Marshal.SizeOf(typeof(MONITORINFOEX));
if (GetMonitorInfo(hMonitor, ref mi))
Console.WriteLine(mi.DeviceName);

return true;
}

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
{
private const int CCHDEVICENAME = 0x20;
private const int CCHFORMNAME = 0x20;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
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 = 0x20)]
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;
}
#elif WINDOWS_UWP
public DisplayInfo GetMainDisplayInfo() =>
GetMainDisplayInfo(null);

Expand Down Expand Up @@ -122,36 +407,9 @@ static DisplayRotation CalculateRotation(DisplayInformation di)
var native = di.NativeOrientation;
var current = di.CurrentOrientation;

if (native == DisplayOrientations.Portrait)
{
switch (current)
{
case DisplayOrientations.Landscape:
return DisplayRotation.Rotation90;
case DisplayOrientations.Portrait:
return DisplayRotation.Rotation0;
case DisplayOrientations.LandscapeFlipped:
return DisplayRotation.Rotation270;
case DisplayOrientations.PortraitFlipped:
return DisplayRotation.Rotation180;
}
}
else if (native == DisplayOrientations.Landscape)
{
switch (current)
{
case DisplayOrientations.Landscape:
return DisplayRotation.Rotation0;
case DisplayOrientations.Portrait:
return DisplayRotation.Rotation270;
case DisplayOrientations.LandscapeFlipped:
return DisplayRotation.Rotation180;
case DisplayOrientations.PortraitFlipped:
return DisplayRotation.Rotation90;
}
}

return DisplayRotation.Unknown;
return CalculateRotation(native, current);
}
}
#endif
}
}
4 changes: 3 additions & 1 deletion src/Essentials/src/DeviceInfo/DeviceInfo.uwp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ static DeviceIdiom GetIdiom()
{
try
{
var uiMode = UIViewSettings.GetForCurrentView().UserInteractionMode;
var currentHandle = Essentials.Platform.CurrentWindowHandle;
var settings = UIViewSettingsInterop.GetForWindow(currentHandle);
var uiMode = settings.UserInteractionMode;
currentIdiom = uiMode == UserInteractionMode.Mouse ? DeviceIdiom.Desktop : DeviceIdiom.Tablet;
}
catch (Exception ex)
Expand Down
Loading