From 358e4e5dec8ba766518a483140569f78a8e03b5b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 23 Aug 2022 22:44:11 +0200 Subject: [PATCH 1/8] Initial code to support the window manager --- .../Handlers/Window/WindowHandler.Windows.cs | 1 + .../EssentialsMauiAppBuilderExtensions.cs | 4 - .../src/Platform/Windows/MauiWinUIWindow.cs | 87 ++++++--------- .../Platform/Windows/NavigationRootManager.cs | 1 + .../src/Platform/Windows/WindowExtensions.cs | 1 + .../Windows/WindowsPlatformMessageIds.cs | 10 -- src/Essentials/src/AppInfo/AppInfo.uwp.cs | 36 ++++--- .../src/DeviceDisplay/DeviceDisplay.uwp.cs | 71 ++++-------- .../src/Platform/ActiveWindowTracker.uwp.cs | 58 ++++++++++ .../src/Platform/Platform.shared.cs | 3 - .../src/Platform/PlatformMethods.uwp.cs} | 34 +++++- .../src/Platform/WindowManager.uwp.cs | 102 ++++++++++++++++++ .../Platform/WindowMessageEventArgs.uwp.cs | 28 +++++ .../src/Platform/WindowStateManager.uwp.cs | 46 +------- .../net-windows/PublicAPI.Unshipped.txt | 3 + 15 files changed, 302 insertions(+), 183 deletions(-) delete mode 100644 src/Core/src/Platform/Windows/WindowsPlatformMessageIds.cs create mode 100644 src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs rename src/{Core/src/Platform/Windows/PlatformMethods.cs => Essentials/src/Platform/PlatformMethods.uwp.cs} (80%) create mode 100644 src/Essentials/src/Platform/WindowManager.uwp.cs create mode 100644 src/Essentials/src/Platform/WindowMessageEventArgs.uwp.cs diff --git a/src/Core/src/Handlers/Window/WindowHandler.Windows.cs b/src/Core/src/Handlers/Window/WindowHandler.Windows.cs index e40a69397e17..b57d6fbdf4fe 100644 --- a/src/Core/src/Handlers/Window/WindowHandler.Windows.cs +++ b/src/Core/src/Handlers/Window/WindowHandler.Windows.cs @@ -1,5 +1,6 @@ using System; using Microsoft.UI.Xaml.Controls; +using Microsoft.Maui.ApplicationModel; namespace Microsoft.Maui.Handlers { diff --git a/src/Core/src/Hosting/EssentialsMauiAppBuilderExtensions.cs b/src/Core/src/Hosting/EssentialsMauiAppBuilderExtensions.cs index d6814ea51f9c..ac1d618950b2 100644 --- a/src/Core/src/Hosting/EssentialsMauiAppBuilderExtensions.cs +++ b/src/Core/src/Hosting/EssentialsMauiAppBuilderExtensions.cs @@ -62,10 +62,6 @@ internal static MauiAppBuilder UseEssentials(this MauiAppBuilder builder) })); #elif WINDOWS life.AddWindows(windows => windows - .OnPlatformMessage((window, args) => - { - ApplicationModel.Platform.OnWindowMessage(args.Hwnd, args.MessageId, args.WParam, args.LParam); - }) .OnActivated((window, args) => { ApplicationModel.Platform.OnActivated(window, args); diff --git a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs index aa4ec68c2b08..124e03b7dd4e 100644 --- a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs +++ b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Devices; using Microsoft.Maui.LifecycleEvents; using Microsoft.UI; @@ -10,11 +11,15 @@ namespace Microsoft.Maui { public class MauiWinUIWindow : UI.Xaml.Window { + readonly WindowManager _windowManager; + IntPtr _windowIcon; bool _enableResumeEvent; public MauiWinUIWindow() { + _windowManager = WindowManager.Get(this); + Activated += OnActivated; Closed += OnClosedPrivate; VisibilityChanged += OnVisibilityChanged; @@ -24,8 +29,36 @@ public MauiWinUIWindow() // and then we can react accordingly ExtendsContentIntoTitleBar = true; - SubClassingWin32(); SetIcon(); + + MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( + del => del(this, new WindowsPlatformWindowSubclassedEventArgs(WindowHandle))); + + _windowManager.WindowMessage += OnWindowMessage; + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + var hWnd = e.Hwnd; + var msg = e.MessageId; + var wParam = e.WParam; + var lParam = e.LParam; + + if (msg == PlatformMethods.MessageIds.WM_SETTINGCHANGE || msg == PlatformMethods.MessageIds.WM_THEMECHANGE) + MauiWinUIApplication.Current.Application?.ThemeChanged(); + + if (msg == PlatformMethods.MessageIds.WM_DPICHANGED) + { + var dpiX = (short)(long)wParam; + var dpiY = (short)((long)wParam >> 16); + + var window = this.GetWindow(); + if (window is not null) + window.DisplayDensityChanged(dpiX / DeviceDisplay.BaseLogicalDpi); + } + + MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( + m => m.Invoke(this, new WindowsPlatformMessageEventArgs(hWnd, msg, wParam, lParam))); + } } protected virtual void OnActivated(object sender, UI.Xaml.WindowActivatedEventArgs args) @@ -62,57 +95,7 @@ protected virtual void OnVisibilityChanged(object sender, UI.Xaml.WindowVisibili MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents(del => del(this, args)); } - #region Platform Window - - IntPtr _hwnd = IntPtr.Zero; - - /// - /// Returns a pointer to the underlying platform window handle (hWnd). - /// - public IntPtr WindowHandle - { - get - { - if (_hwnd == IntPtr.Zero) - _hwnd = this.GetWindowHandle(); - return _hwnd; - } - } - - PlatformMethods.WindowProc? newWndProc = null; - IntPtr oldWndProc = IntPtr.Zero; - - void SubClassingWin32() - { - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( - del => del(this, new WindowsPlatformWindowSubclassedEventArgs(WindowHandle))); - - newWndProc = new PlatformMethods.WindowProc(NewWindowProc); - oldWndProc = PlatformMethods.SetWindowLongPtr(WindowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, newWndProc); - - IntPtr NewWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - if (msg == WindowsPlatformMessageIds.WM_SETTINGCHANGE || msg == WindowsPlatformMessageIds.WM_THEMECHANGE) - MauiWinUIApplication.Current.Application?.ThemeChanged(); - - if (msg == WindowsPlatformMessageIds.WM_DPICHANGED) - { - var dpiX = (short)(long)wParam; - var dpiY = (short)((long)wParam >> 16); - - var window = this.GetWindow(); - if (window is not null) - window.DisplayDensityChanged(dpiX / DeviceDisplay.BaseLogicalDpi); - } - - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( - m => m.Invoke(this, new WindowsPlatformMessageEventArgs(hWnd, msg, wParam, lParam))); - - return PlatformMethods.CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam); - } - } - - #endregion + public IntPtr WindowHandle => _windowManager.WindowHandle; /// /// Default the Window Icon to the icon stored in the .exe, if any. diff --git a/src/Core/src/Platform/Windows/NavigationRootManager.cs b/src/Core/src/Platform/Windows/NavigationRootManager.cs index d5a658e39f63..85307bd148bc 100644 --- a/src/Core/src/Platform/Windows/NavigationRootManager.cs +++ b/src/Core/src/Platform/Windows/NavigationRootManager.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Maui.ApplicationModel; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; diff --git a/src/Core/src/Platform/Windows/WindowExtensions.cs b/src/Core/src/Platform/Windows/WindowExtensions.cs index f2073894d8fa..572b37501f82 100644 --- a/src/Core/src/Platform/Windows/WindowExtensions.cs +++ b/src/Core/src/Platform/Windows/WindowExtensions.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Devices; using System.Threading.Tasks; using Microsoft.Maui.Media; diff --git a/src/Core/src/Platform/Windows/WindowsPlatformMessageIds.cs b/src/Core/src/Platform/Windows/WindowsPlatformMessageIds.cs deleted file mode 100644 index 6f2e36348d88..000000000000 --- a/src/Core/src/Platform/Windows/WindowsPlatformMessageIds.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.Maui.Platform -{ - internal static class WindowsPlatformMessageIds - { - public const int WM_DPICHANGED = 0x02E0; - public const int WM_DISPLAYCHANGE = 0x007E; - public const int WM_SETTINGCHANGE = 0x001A; - public const int WM_THEMECHANGE = 0x031A; - } -} \ No newline at end of file diff --git a/src/Essentials/src/AppInfo/AppInfo.uwp.cs b/src/Essentials/src/AppInfo/AppInfo.uwp.cs index f81b90962695..0dca0f227b22 100644 --- a/src/Essentials/src/AppInfo/AppInfo.uwp.cs +++ b/src/Essentials/src/AppInfo/AppInfo.uwp.cs @@ -1,13 +1,8 @@ using System; -using System.Globalization; -using Windows.ApplicationModel; using System.Diagnostics; +using System.Globalization; using System.Reflection; -#if WINDOWS -using Microsoft.UI.Xaml; -#else -using Windows.UI.Xaml; -#endif +using Windows.ApplicationModel; namespace Microsoft.Maui.ApplicationModel { @@ -17,13 +12,15 @@ class AppInfoImplementation : IAppInfo const string SettingsUri = "ms-settings:appsfeatures-app"; - ApplicationTheme? _applicationTheme; + UI.Xaml.ApplicationTheme? _applicationTheme; + + readonly ActiveWindowTracker _activeWindowTracker; public AppInfoImplementation() { - // TODO: NET7 use new public events - if (WindowStateManager.Default is WindowStateManagerImplementation impl) - impl.ActiveWindowThemeChanged += OnActiveWindowThemeChanged; + _activeWindowTracker = new(WindowStateManager.Default); + _activeWindowTracker.Start(); + _activeWindowTracker.WindowMessage += OnWindowMessage; if (MainThread.IsMainThread) OnActiveWindowThemeChanged(); @@ -62,12 +59,12 @@ public AppTheme RequestedTheme { get { - if (MainThread.IsMainThread && Application.Current != null) - _applicationTheme = Application.Current.RequestedTheme; + if (MainThread.IsMainThread && UI.Xaml.Application.Current != null) + _applicationTheme = UI.Xaml.Application.Current.RequestedTheme; else if (_applicationTheme == null) return AppTheme.Unspecified; - return _applicationTheme == ApplicationTheme.Dark ? AppTheme.Dark : AppTheme.Light; + return _applicationTheme == UI.Xaml.ApplicationTheme.Dark ? AppTheme.Dark : AppTheme.Light; } } @@ -78,9 +75,16 @@ public AppTheme RequestedTheme public LayoutDirection RequestedLayoutDirection => CultureInfo.CurrentCulture.TextInfo.IsRightToLeft ? LayoutDirection.RightToLeft : LayoutDirection.LeftToRight; - void OnActiveWindowThemeChanged(object sender = null, EventArgs e = null) + void OnWindowMessage(object sender, WindowMessageEventArgs e) + { + if (e.MessageId == PlatformMethods.MessageIds.WM_SETTINGCHANGE || + e.MessageId == PlatformMethods.MessageIds.WM_THEMECHANGE) + OnActiveWindowThemeChanged(); + } + + void OnActiveWindowThemeChanged() { - if (Application.Current is Application app) + if (UI.Xaml.Application.Current is UI.Xaml.Application app) _applicationTheme = app.RequestedTheme; } } diff --git a/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs b/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs index 9161e1a872dd..6e581585e711 100644 --- a/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs +++ b/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs @@ -11,8 +11,16 @@ namespace Microsoft.Maui.Devices partial class DeviceDisplayImplementation { readonly object locker = new object(); + readonly ActiveWindowTracker _activeWindowTracker; + DisplayRequest? displayRequest; + public DeviceDisplayImplementation() + { + _activeWindowTracker = new(WindowStateManager.Default); + _activeWindowTracker.WindowMessage += OnWindowMessage; + } + protected override bool GetKeepScreenOn() { lock (locker) @@ -78,8 +86,6 @@ static DisplayRotation CalculateRotation(DisplayOrientations native, DisplayOrie return DisplayRotation.Unknown; } - AppWindow? _currentAppWindowListeningTo; - protected override DisplayInfo GetMainDisplayInfo() { if (WindowStateManager.Default.GetActiveAppWindow(false) is not AppWindow appWindow) @@ -138,47 +144,22 @@ protected override DisplayInfo GetMainDisplayInfo() return null; } - protected override void StartScreenMetricsListeners() - { - MainThread.BeginInvokeOnMainThread(() => - { - WindowStateManager.Default.ActiveWindowDisplayChanged += OnWindowDisplayChanged; - WindowStateManager.Default.ActiveWindowChanged += OnCurrentWindowChanged; + protected override void StartScreenMetricsListeners() => + MainThread.BeginInvokeOnMainThread(_activeWindowTracker.Start); - _currentAppWindowListeningTo = WindowStateManager.Default.GetActiveAppWindow(true)!; - _currentAppWindowListeningTo.Changed += OnAppWindowChanged; - }); - } + protected override void StopScreenMetricsListeners() => + MainThread.BeginInvokeOnMainThread(_activeWindowTracker.Stop); - protected override void StopScreenMetricsListeners() + // 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) { - MainThread.BeginInvokeOnMainThread(() => - { - WindowStateManager.Default.ActiveWindowChanged -= OnCurrentWindowChanged; - WindowStateManager.Default.ActiveWindowDisplayChanged -= OnWindowDisplayChanged; - - if (_currentAppWindowListeningTo != null) - _currentAppWindowListeningTo.Changed -= OnAppWindowChanged; - - _currentAppWindowListeningTo = null; - }); + if (e.MessageId == PlatformMethods.MessageIds.WM_DISPLAYCHANGE || + e.MessageId == PlatformMethods.MessageIds.WM_DPICHANGED) + OnMainDisplayInfoChanged(); } - void OnCurrentWindowChanged(object? sender, EventArgs e) - { - if (_currentAppWindowListeningTo != null) - _currentAppWindowListeningTo.Changed -= OnAppWindowChanged; - - _currentAppWindowListeningTo = WindowStateManager.Default.GetActiveAppWindow(true)!; - _currentAppWindowListeningTo.Changed += OnAppWindowChanged; - } - - void OnWindowDisplayChanged(object? sender, EventArgs e) => - OnMainDisplayInfoChanged(); - - void OnAppWindowChanged(AppWindow sender, AppWindowChangedEventArgs args) => - OnMainDisplayInfoChanged(); - static DisplayRotation CalculateRotation(DEVMODE devMode, AppWindow appWindow) { DisplayOrientations native = DisplayOrientations.Portrait; @@ -236,20 +217,6 @@ static extern Boolean EnumDisplaySettings( [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, diff --git a/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs b/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs new file mode 100644 index 000000000000..16fb6c7d1c15 --- /dev/null +++ b/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs @@ -0,0 +1,58 @@ +#nullable enable +using System; + +namespace Microsoft.Maui.ApplicationModel +{ + class ActiveWindowTracker + { + readonly IWindowStateManager _windowStateManager; + + WindowManager? _currentWindowManager; + + public ActiveWindowTracker(IWindowStateManager windowStateManager) + { + _windowStateManager = windowStateManager; + } + + public event EventHandler? WindowMessage; + + public void Start() + { + var window = _windowStateManager.GetActiveWindow(); + OnActiveWindowChanged(window); + + _windowStateManager.ActiveWindowChanged += OnActiveWindowChanged; + } + + public void Stop() + { + OnActiveWindowChanged(null); + + _windowStateManager.ActiveWindowChanged -= OnActiveWindowChanged; + } + + void OnActiveWindowChanged(object? sender, EventArgs e) + { + var window = _windowStateManager?.GetActiveWindow(); + OnActiveWindowChanged(window); + } + + void OnActiveWindowChanged(UI.Xaml.Window? window) + { + if (_currentWindowManager is not null) + { + _currentWindowManager.WindowMessage -= OnWindowMessage; + _currentWindowManager = null; + } + + if (window is not null) + { + _currentWindowManager = WindowManager.Get(window); + _currentWindowManager.WindowMessage += OnWindowMessage; + } + } + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) => + WindowMessage?.Invoke(sender, e); + } +} diff --git a/src/Essentials/src/Platform/Platform.shared.cs b/src/Essentials/src/Platform/Platform.shared.cs index e25aae751190..ecdf3a90b549 100644 --- a/src/Essentials/src/Platform/Platform.shared.cs +++ b/src/Essentials/src/Platform/Platform.shared.cs @@ -82,9 +82,6 @@ public static void OnLaunched(UI.Xaml.LaunchActivatedEventArgs e) => public static void OnActivated(UI.Xaml.Window window, UI.Xaml.WindowActivatedEventArgs args) => WindowStateManager.Default.OnActivated(window, args); - public static void OnWindowMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) => - WindowStateManager.Default.OnWindowMessage(hWnd, msg, wParam, lParam); - #elif TIZEN public static Tizen.Applications.Package CurrentPackage { diff --git a/src/Core/src/Platform/Windows/PlatformMethods.cs b/src/Essentials/src/Platform/PlatformMethods.uwp.cs similarity index 80% rename from src/Core/src/Platform/Windows/PlatformMethods.cs rename to src/Essentials/src/Platform/PlatformMethods.uwp.cs index c65d7043a9ce..56f63e8aa5ae 100644 --- a/src/Core/src/Platform/Windows/PlatformMethods.cs +++ b/src/Essentials/src/Platform/PlatformMethods.uwp.cs @@ -1,11 +1,19 @@ -#nullable enable +#nullable enable using System; using System.Runtime.InteropServices; -namespace Microsoft.Maui.Platform +namespace Microsoft.Maui.ApplicationModel { static class PlatformMethods { + public static class MessageIds + { + public const int WM_DPICHANGED = 0x02E0; + public const int WM_DISPLAYCHANGE = 0x007E; + public const int WM_SETTINGCHANGE = 0x001A; + public const int WM_THEMECHANGE = 0x031A; + } + public delegate IntPtr WindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); public static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongFlags nIndex, WindowProc dwNewLong) @@ -22,6 +30,20 @@ public static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongFlags nIndex, Windo static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongFlags nIndex, WindowProc dwNewLong); } + public static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongFlags nIndex, IntPtr dwNewLong) + { + if (IntPtr.Size == 8) + return SetWindowLongPtr64(hWnd, nIndex, dwNewLong); + else + return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong)); + + [DllImport("user32.dll", EntryPoint = "SetWindowLong")] + static extern int SetWindowLong32(IntPtr hWnd, WindowLongFlags nIndex, IntPtr dwNewLong); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] + static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongFlags nIndex, IntPtr dwNewLong); + } + public static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongFlags nIndex, long dwNewLong) { if (IntPtr.Size == 8) @@ -51,15 +73,17 @@ public static long GetWindowLongPtr(IntPtr hWnd, WindowLongFlags nIndex) } [DllImport("user32.dll")] - public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int width, int height, SetWindowPosFlags uFlags); + [DllImport("comctl32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr DefSubclassProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); + [DllImport("user32.dll")] public static extern uint GetDpiForWindow(IntPtr hWnd); - [DllImport("User32", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); @@ -156,4 +180,4 @@ struct RECT public int Bottom; } } -} \ No newline at end of file +} diff --git a/src/Essentials/src/Platform/WindowManager.uwp.cs b/src/Essentials/src/Platform/WindowManager.uwp.cs new file mode 100644 index 000000000000..604d161bb1ce --- /dev/null +++ b/src/Essentials/src/Platform/WindowManager.uwp.cs @@ -0,0 +1,102 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.UI.Xaml; + +namespace Microsoft.Maui.ApplicationModel +{ + class WindowManager : IDisposable + { + readonly static Dictionary> _managers = new(); + readonly static PlatformMethods.WindowProc _newWndProc = new(NewWindowProc); + + readonly IntPtr _windowHandle; + readonly IntPtr _oldWndProc; + + bool _isDisposed; + + WindowManager(Window window) + { + _windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(window); + _oldWndProc = PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _newWndProc); + } + + public IntPtr WindowHandle => _windowHandle; + + public event EventHandler? WindowMessage; + + static IntPtr NewWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam) + { + if (_managers.TryGetValue(hWnd, out var weakManager) && weakManager.TryGetTarget(out var manager)) + { + var evt = manager.WindowMessage; + if (evt is not null) + { + var args = new WindowMessageEventArgs(hWnd, uMsg, wParam, lParam); + + evt.Invoke(manager, args); + + if (args.Handled) + return args.Result; + } + + return PlatformMethods.CallWindowProc(manager._oldWndProc, hWnd, uMsg, wParam, lParam); + } + + // this technically should never happen + return PlatformMethods.DefSubclassProc(hWnd, uMsg, wParam, lParam); + } + + public static WindowManager Get(Window window) + { + var handle = WinRT.Interop.WindowNative.GetWindowHandle(window); + + if (_managers.TryGetValue(handle, out var weakManager) && + weakManager.TryGetTarget(out var manager) && + !manager._isDisposed) + return manager; + + var newManager = new WindowManager(window); + + _managers[handle] = new WeakReference(newManager); + + return newManager; + } + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + // dispose managed state (managed objects) + + if (_managers.ContainsKey(_windowHandle)) + _managers.Remove(_windowHandle); + } + + // free unmanaged resources (unmanaged objects) and override finalizer + + var newPtr = PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _oldWndProc); + var p = Marshal.GetFunctionPointerForDelegate(_newWndProc); + var same = p == newPtr; + + // set large fields to null + + _isDisposed = true; + } + } + + ~WindowManager() + { + Dispose(disposing: false); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Essentials/src/Platform/WindowMessageEventArgs.uwp.cs b/src/Essentials/src/Platform/WindowMessageEventArgs.uwp.cs new file mode 100644 index 000000000000..d817a4925c27 --- /dev/null +++ b/src/Essentials/src/Platform/WindowMessageEventArgs.uwp.cs @@ -0,0 +1,28 @@ +#nullable enable +using System; + +namespace Microsoft.Maui.ApplicationModel +{ + class WindowMessageEventArgs : EventArgs + { + public WindowMessageEventArgs(IntPtr hwnd, uint messageId, IntPtr wParam, IntPtr lParam) + { + Hwnd = hwnd; + MessageId = messageId; + WParam = wParam; + LParam = lParam; + } + + public IntPtr Hwnd { get; } + + public uint MessageId { get; } + + public IntPtr WParam { get; } + + public IntPtr LParam { get; } + + public IntPtr Result { get; set; } + + public bool Handled { get; set; } + } +} diff --git a/src/Essentials/src/Platform/WindowStateManager.uwp.cs b/src/Essentials/src/Platform/WindowStateManager.uwp.cs index 74a454a16cd5..ba242eb73a74 100644 --- a/src/Essentials/src/Platform/WindowStateManager.uwp.cs +++ b/src/Essentials/src/Platform/WindowStateManager.uwp.cs @@ -9,16 +9,9 @@ public interface IWindowStateManager { event EventHandler ActiveWindowChanged; - event EventHandler ActiveWindowDisplayChanged; - - // TODO: NET7 make this public - // event EventHandler ActiveWindowThemeChanged; - Window? GetActiveWindow(); void OnActivated(Window window, WindowActivatedEventArgs args); - - void OnWindowMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); } public static class WindowStateManager @@ -76,56 +69,27 @@ public static IntPtr GetActiveWindowHandle(this IWindowStateManager manager, boo class WindowStateManagerImplementation : IWindowStateManager { - const uint WM_DISPLAYCHANGE = 0x7E; - const uint WM_DPICHANGED = 0x02E0; - const uint WM_SETTINGCHANGE = 0x001A; - const uint WM_THEMECHANGE = 0x031A; - Window? _activeWindow; - IntPtr _activeWindowHandle; public event EventHandler? ActiveWindowChanged; - public event EventHandler? ActiveWindowDisplayChanged; - - public event EventHandler? ActiveWindowThemeChanged; - public Window? GetActiveWindow() => _activeWindow; - void SetActiveWindow(Window window) - { - if (_activeWindow == window) - return; - - _activeWindow = window; - _activeWindowHandle = window is null - ? IntPtr.Zero - : WinRT.Interop.WindowNative.GetWindowHandle(window); - - ActiveWindowChanged?.Invoke(window, EventArgs.Empty); - } - public void OnActivated(Window window, WindowActivatedEventArgs args) { if (args.WindowActivationState != WindowActivationState.Deactivated) SetActiveWindow(window); } - // Currently there isn't a way to detect Orientation Changes unless you subclass the WinUI.Window and watch the messages - // Maui.Core forwards these messages to here so that WinUI can react accordingly. - // 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 - public void OnWindowMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + void SetActiveWindow(Window window) { - // only track events if they come from the active window - if (_activeWindow is null || hWnd != _activeWindowHandle) + if (_activeWindow == window) return; - if (msg == WM_SETTINGCHANGE || msg == WM_THEMECHANGE) - ActiveWindowThemeChanged?.Invoke(_activeWindow, EventArgs.Empty); - else if (msg == WM_DISPLAYCHANGE || msg == WM_DPICHANGED) - ActiveWindowDisplayChanged?.Invoke(_activeWindow, EventArgs.Empty); + _activeWindow = window; + + ActiveWindowChanged?.Invoke(window, EventArgs.Empty); } } } diff --git a/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt index ab058de62d44..779967737f4d 100644 --- a/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +*REMOVED*Microsoft.Maui.ApplicationModel.IWindowStateManager.ActiveWindowDisplayChanged -> System.EventHandler! +*REMOVED*Microsoft.Maui.ApplicationModel.IWindowStateManager.OnWindowMessage(System.IntPtr hWnd, uint msg, System.IntPtr wParam, System.IntPtr lParam) -> void +*REMOVED*static Microsoft.Maui.ApplicationModel.Platform.OnWindowMessage(System.IntPtr hWnd, uint msg, System.IntPtr wParam, System.IntPtr lParam) -> void From 5dbc4204740106e71354cb66339ecc1386ab70fa Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 24 Aug 2022 00:22:05 +0200 Subject: [PATCH 2/8] swap --- .../src/Platform/Windows/MauiWinUIWindow.cs | 59 ++++++++++--------- .../src/Platform/WindowStateManager.uwp.cs | 12 ++-- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs index 124e03b7dd4e..39c56f9f8868 100644 --- a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs +++ b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs @@ -29,36 +29,8 @@ public MauiWinUIWindow() // and then we can react accordingly ExtendsContentIntoTitleBar = true; + SubClassingWin32(); SetIcon(); - - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( - del => del(this, new WindowsPlatformWindowSubclassedEventArgs(WindowHandle))); - - _windowManager.WindowMessage += OnWindowMessage; - - void OnWindowMessage(object? sender, WindowMessageEventArgs e) - { - var hWnd = e.Hwnd; - var msg = e.MessageId; - var wParam = e.WParam; - var lParam = e.LParam; - - if (msg == PlatformMethods.MessageIds.WM_SETTINGCHANGE || msg == PlatformMethods.MessageIds.WM_THEMECHANGE) - MauiWinUIApplication.Current.Application?.ThemeChanged(); - - if (msg == PlatformMethods.MessageIds.WM_DPICHANGED) - { - var dpiX = (short)(long)wParam; - var dpiY = (short)((long)wParam >> 16); - - var window = this.GetWindow(); - if (window is not null) - window.DisplayDensityChanged(dpiX / DeviceDisplay.BaseLogicalDpi); - } - - MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( - m => m.Invoke(this, new WindowsPlatformMessageEventArgs(hWnd, msg, wParam, lParam))); - } } protected virtual void OnActivated(object sender, UI.Xaml.WindowActivatedEventArgs args) @@ -97,6 +69,35 @@ protected virtual void OnVisibilityChanged(object sender, UI.Xaml.WindowVisibili public IntPtr WindowHandle => _windowManager.WindowHandle; + void SubClassingWin32() + { + MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( + del => del(this, new WindowsPlatformWindowSubclassedEventArgs(WindowHandle))); + + _windowManager.WindowMessage += OnWindowMessage; + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + if (e.MessageId == PlatformMethods.MessageIds.WM_SETTINGCHANGE || + e.MessageId == PlatformMethods.MessageIds.WM_THEMECHANGE) + { + MauiWinUIApplication.Current.Application?.ThemeChanged(); + } + else if (e.MessageId == PlatformMethods.MessageIds.WM_DPICHANGED) + { + var dpiX = (short)(long)e.WParam; + var dpiY = (short)((long)e.WParam >> 16); + + var window = this.GetWindow(); + if (window is not null) + window.DisplayDensityChanged(dpiX / DeviceDisplay.BaseLogicalDpi); + } + + MauiWinUIApplication.Current.Services?.InvokeLifecycleEvents( + m => m.Invoke(this, new WindowsPlatformMessageEventArgs(e.Hwnd, e.MessageId, e.WParam, e.LParam))); + } + } + /// /// Default the Window Icon to the icon stored in the .exe, if any. /// diff --git a/src/Essentials/src/Platform/WindowStateManager.uwp.cs b/src/Essentials/src/Platform/WindowStateManager.uwp.cs index ba242eb73a74..e6848ecab142 100644 --- a/src/Essentials/src/Platform/WindowStateManager.uwp.cs +++ b/src/Essentials/src/Platform/WindowStateManager.uwp.cs @@ -76,12 +76,6 @@ class WindowStateManagerImplementation : IWindowStateManager public Window? GetActiveWindow() => _activeWindow; - public void OnActivated(Window window, WindowActivatedEventArgs args) - { - if (args.WindowActivationState != WindowActivationState.Deactivated) - SetActiveWindow(window); - } - void SetActiveWindow(Window window) { if (_activeWindow == window) @@ -91,5 +85,11 @@ void SetActiveWindow(Window window) ActiveWindowChanged?.Invoke(window, EventArgs.Empty); } + + public void OnActivated(Window window, WindowActivatedEventArgs args) + { + if (args.WindowActivationState != WindowActivationState.Deactivated) + SetActiveWindow(window); + } } } From cdc3bf1c9fbd11b4976f12ba5dfafe11bfdfc275 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 24 Aug 2022 00:33:10 +0200 Subject: [PATCH 3/8] rename --- .../src/Platform/Windows/MauiWinUIWindow.cs | 4 ++-- .../src/Platform/ActiveWindowTracker.uwp.cs | 4 ++-- .../src/Platform/WindowManager.uwp.cs | 21 +++++++++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs index 39c56f9f8868..174fdf1398a9 100644 --- a/src/Core/src/Platform/Windows/MauiWinUIWindow.cs +++ b/src/Core/src/Platform/Windows/MauiWinUIWindow.cs @@ -11,14 +11,14 @@ namespace Microsoft.Maui { public class MauiWinUIWindow : UI.Xaml.Window { - readonly WindowManager _windowManager; + readonly WindowMessageManager _windowManager; IntPtr _windowIcon; bool _enableResumeEvent; public MauiWinUIWindow() { - _windowManager = WindowManager.Get(this); + _windowManager = WindowMessageManager.Get(this); Activated += OnActivated; Closed += OnClosedPrivate; diff --git a/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs b/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs index 16fb6c7d1c15..8bafa17572ad 100644 --- a/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs +++ b/src/Essentials/src/Platform/ActiveWindowTracker.uwp.cs @@ -7,7 +7,7 @@ class ActiveWindowTracker { readonly IWindowStateManager _windowStateManager; - WindowManager? _currentWindowManager; + WindowMessageManager? _currentWindowManager; public ActiveWindowTracker(IWindowStateManager windowStateManager) { @@ -47,7 +47,7 @@ void OnActiveWindowChanged(UI.Xaml.Window? window) if (window is not null) { - _currentWindowManager = WindowManager.Get(window); + _currentWindowManager = WindowMessageManager.Get(window); _currentWindowManager.WindowMessage += OnWindowMessage; } } diff --git a/src/Essentials/src/Platform/WindowManager.uwp.cs b/src/Essentials/src/Platform/WindowManager.uwp.cs index 604d161bb1ce..39a71dd88383 100644 --- a/src/Essentials/src/Platform/WindowManager.uwp.cs +++ b/src/Essentials/src/Platform/WindowManager.uwp.cs @@ -6,17 +6,17 @@ namespace Microsoft.Maui.ApplicationModel { - class WindowManager : IDisposable + class WindowMessageManager : IDisposable { - readonly static Dictionary> _managers = new(); + readonly static Dictionary> _managers = new(); readonly static PlatformMethods.WindowProc _newWndProc = new(NewWindowProc); - readonly IntPtr _windowHandle; - readonly IntPtr _oldWndProc; + IntPtr _windowHandle; + IntPtr _oldWndProc; bool _isDisposed; - WindowManager(Window window) + WindowMessageManager(Window window) { _windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(window); _oldWndProc = PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _newWndProc); @@ -48,7 +48,7 @@ static IntPtr NewWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam return PlatformMethods.DefSubclassProc(hWnd, uMsg, wParam, lParam); } - public static WindowManager Get(Window window) + public static WindowMessageManager Get(Window window) { var handle = WinRT.Interop.WindowNative.GetWindowHandle(window); @@ -57,9 +57,9 @@ public static WindowManager Get(Window window) !manager._isDisposed) return manager; - var newManager = new WindowManager(window); + var newManager = new WindowMessageManager(window); - _managers[handle] = new WeakReference(newManager); + _managers[handle] = new WeakReference(newManager); return newManager; } @@ -84,11 +84,14 @@ protected virtual void Dispose(bool disposing) // set large fields to null + _windowHandle = IntPtr.Zero; + _oldWndProc = IntPtr.Zero; + _isDisposed = true; } } - ~WindowManager() + ~WindowMessageManager() { Dispose(disposing: false); } From a10b22b760fd16339f86f9b6613650b3cc9f2e2f Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 24 Aug 2022 00:35:22 +0200 Subject: [PATCH 4/8] ws --- src/Essentials/src/AppInfo/AppInfo.uwp.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Essentials/src/AppInfo/AppInfo.uwp.cs b/src/Essentials/src/AppInfo/AppInfo.uwp.cs index 0dca0f227b22..99e01f236278 100644 --- a/src/Essentials/src/AppInfo/AppInfo.uwp.cs +++ b/src/Essentials/src/AppInfo/AppInfo.uwp.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Globalization; using System.Reflection; +using Microsoft.UI.Xaml; using Windows.ApplicationModel; namespace Microsoft.Maui.ApplicationModel @@ -12,7 +13,7 @@ class AppInfoImplementation : IAppInfo const string SettingsUri = "ms-settings:appsfeatures-app"; - UI.Xaml.ApplicationTheme? _applicationTheme; + ApplicationTheme? _applicationTheme; readonly ActiveWindowTracker _activeWindowTracker; @@ -59,12 +60,12 @@ public AppTheme RequestedTheme { get { - if (MainThread.IsMainThread && UI.Xaml.Application.Current != null) - _applicationTheme = UI.Xaml.Application.Current.RequestedTheme; + if (MainThread.IsMainThread && Application.Current != null) + _applicationTheme = Application.Current.RequestedTheme; else if (_applicationTheme == null) return AppTheme.Unspecified; - return _applicationTheme == UI.Xaml.ApplicationTheme.Dark ? AppTheme.Dark : AppTheme.Light; + return _applicationTheme == ApplicationTheme.Dark ? AppTheme.Dark : AppTheme.Light; } } @@ -84,7 +85,7 @@ void OnWindowMessage(object sender, WindowMessageEventArgs e) void OnActiveWindowThemeChanged() { - if (UI.Xaml.Application.Current is UI.Xaml.Application app) + if (Application.Current is Application app) _applicationTheme = app.RequestedTheme; } } From b159c09640782b44f53daa3b9e013ecd5d0a6a10 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 24 Aug 2022 21:55:04 +0200 Subject: [PATCH 5/8] Add unit tests for WindowMessageManager --- ...ger.uwp.cs => WindowMessageManager.uwp.cs} | 15 ++- .../Windows/WindowMessageManager_Tests.cs | 119 ++++++++++++++++++ src/Essentials/test/DeviceTests/Utils.cs | 100 +-------------- 3 files changed, 135 insertions(+), 99 deletions(-) rename src/Essentials/src/Platform/{WindowManager.uwp.cs => WindowMessageManager.uwp.cs} (87%) create mode 100644 src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs diff --git a/src/Essentials/src/Platform/WindowManager.uwp.cs b/src/Essentials/src/Platform/WindowMessageManager.uwp.cs similarity index 87% rename from src/Essentials/src/Platform/WindowManager.uwp.cs rename to src/Essentials/src/Platform/WindowMessageManager.uwp.cs index 39a71dd88383..5c0d0d743f95 100644 --- a/src/Essentials/src/Platform/WindowManager.uwp.cs +++ b/src/Essentials/src/Platform/WindowMessageManager.uwp.cs @@ -1,7 +1,7 @@ #nullable enable using System; using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Linq; using Microsoft.UI.Xaml; namespace Microsoft.Maui.ApplicationModel @@ -24,6 +24,15 @@ class WindowMessageManager : IDisposable public IntPtr WindowHandle => _windowHandle; + public static IEnumerable GetAll() + { + foreach (var weakManager in _managers.Values.ToArray()) + { + if (weakManager.TryGetTarget(out var manager)) + yield return manager; + } + } + public event EventHandler? WindowMessage; static IntPtr NewWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam) @@ -78,9 +87,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer - var newPtr = PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _oldWndProc); - var p = Marshal.GetFunctionPointerForDelegate(_newWndProc); - var same = p == newPtr; + PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _oldWndProc); // set large fields to null diff --git a/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs b/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs new file mode 100644 index 000000000000..43c4f0e088ad --- /dev/null +++ b/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Platform; +using Xunit; + +namespace Microsoft.Maui.Essentials.DeviceTests.Shared +{ + [Category("Windows WindowMessageManager")] + public class Windows_WindowMessageManager_Tests + { + [Fact] + public Task NewWindowIsNotFound() => + Utils.OnMainThread(() => + { + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var allManagers = WindowMessageManager.GetAll().ToArray(); + var allHandles = allManagers.Select(m => m.WindowHandle).ToArray(); + + Assert.DoesNotContain(handle, allHandles); + }); + + [Fact] + public Task RegisteredWindowIsFound() => + Utils.OnMainThread(() => + { + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + + var allManagers = WindowMessageManager.GetAll().ToArray(); + var allHandles = allManagers.Select(m => m.WindowHandle).ToArray(); + + Assert.Contains(handle, allHandles); + Assert.Contains(manager, allManagers); + }); + + [Fact] + public Task DisposedManagerIsNotFound() => + Utils.OnMainThread(() => + { + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + + manager.Dispose(); + + var allManagers = WindowMessageManager.GetAll().ToArray(); + var allHandles = allManagers.Select(m => m.WindowHandle).ToArray(); + + Assert.DoesNotContain(handle, allHandles); + Assert.DoesNotContain(manager, allManagers); + }); + + [Fact] + public Task PostingMessagesReachesEvent() => + Utils.OnMainThread(async () => + { + const uint message = WM_APP + 1; + var messages = new List(); + + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + manager.WindowMessage += OnWindowMessage; + + PostMessage(handle, message, IntPtr.Zero, IntPtr.Zero); + + await Task.Delay(100); + + Assert.Contains(message, messages); + + void OnWindowMessage(object sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); + + [Fact] + public Task PostingMessagesToDisposedManagerDoesNotReachEvent() => + Utils.OnMainThread(async () => + { + const uint message = WM_APP + 1; + var messages = new List(); + + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + manager.WindowMessage += OnWindowMessage; + manager.Dispose(); + + PostMessage(handle, message, IntPtr.Zero, IntPtr.Zero); + + await Task.Delay(100); + + Assert.DoesNotContain(message, messages); + + void OnWindowMessage(object sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); + + const uint WM_APP = 0x8000; + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + } +} diff --git a/src/Essentials/test/DeviceTests/Utils.cs b/src/Essentials/test/DeviceTests/Utils.cs index ecf712244881..e1bdb812f08f 100644 --- a/src/Essentials/test/DeviceTests/Utils.cs +++ b/src/Essentials/test/DeviceTests/Utils.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; namespace Microsoft.Maui.Essentials.DeviceTests { @@ -7,101 +8,10 @@ public class Utils { public static void Unused(params object[] obj) { } -#if WINDOWS_UWP || WINDOWS - public static async Task OnMainThread(global::Windows.UI.Core.DispatchedHandler action) - { - var mainView = global::Windows.ApplicationModel.Core.CoreApplication.MainView; - var normal = global::Windows.UI.Core.CoreDispatcherPriority.Normal; - await mainView.CoreWindow.Dispatcher.RunAsync(normal, action); - } + public static Task OnMainThread(Action action) => + MainThread.InvokeOnMainThreadAsync(() => action()); - public static Task OnMainThread(Func action) - { - var tcs = new TaskCompletionSource(); - var mainView = global::Windows.ApplicationModel.Core.CoreApplication.MainView; - var normal = global::Windows.UI.Core.CoreDispatcherPriority.Normal; -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - mainView.CoreWindow.Dispatcher.RunAsync(normal, async () => - { - try - { - await action(); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - return tcs.Task; - } -#elif __ANDROID__ - public static Task OnMainThread(Action action) - { - var tcs = new TaskCompletionSource(); - var looper = Android.OS.Looper.MainLooper; - var handler = new Android.OS.Handler(looper); - handler.Post(() => - { - try - { - action(); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); - return tcs.Task; - } - - public static Task OnMainThread(Func action) - { - var tcs = new TaskCompletionSource(); - var looper = Android.OS.Looper.MainLooper; - var handler = new Android.OS.Handler(looper); - handler.Post(async () => - { - try - { - await action(); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); - return tcs.Task; - } -#elif __IOS__ - public static Task OnMainThread(Action action) - { - var obj = new Foundation.NSObject(); - obj.InvokeOnMainThread(action); - return Task.FromResult(true); - } - - public static Task OnMainThread(Func action) - { - var tcs = new TaskCompletionSource(); - var obj = new Foundation.NSObject(); - obj.InvokeOnMainThread(async () => - { - try - { - await action(); - tcs.SetResult(true); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); - return tcs.Task; - } -#endif + public static Task OnMainThread(Func action) => + MainThread.InvokeOnMainThreadAsync(() => action()); } } From 6b8c94ee8edd1c036891869bcef1d23d5dff4ea5 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 24 Aug 2022 22:52:30 +0200 Subject: [PATCH 6/8] More device tests for windows --- .../src/Platform/WindowMessageManager.uwp.cs | 52 +++++- .../Windows/ActiveWindowTracker_Tests.cs | 163 ++++++++++++++++++ .../Windows/BaseWindowMessageManager_Tests.cs | 27 +++ .../Windows/WindowMessageManager_Tests.cs | 135 +++++++++++++-- 4 files changed, 354 insertions(+), 23 deletions(-) create mode 100644 src/Essentials/test/DeviceTests/Tests/Windows/ActiveWindowTracker_Tests.cs create mode 100644 src/Essentials/test/DeviceTests/Tests/Windows/BaseWindowMessageManager_Tests.cs diff --git a/src/Essentials/src/Platform/WindowMessageManager.uwp.cs b/src/Essentials/src/Platform/WindowMessageManager.uwp.cs index 5c0d0d743f95..f47b7d45284d 100644 --- a/src/Essentials/src/Platform/WindowMessageManager.uwp.cs +++ b/src/Essentials/src/Platform/WindowMessageManager.uwp.cs @@ -11,19 +11,24 @@ class WindowMessageManager : IDisposable readonly static Dictionary> _managers = new(); readonly static PlatformMethods.WindowProc _newWndProc = new(NewWindowProc); + readonly object _locker = new(); + IntPtr _windowHandle; IntPtr _oldWndProc; bool _isDisposed; + event EventHandler? WindowMessageInternal; + WindowMessageManager(Window window) { _windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(window); - _oldWndProc = PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _newWndProc); } public IntPtr WindowHandle => _windowHandle; + public bool IsAttached => _oldWndProc != IntPtr.Zero; + public static IEnumerable GetAll() { foreach (var weakManager in _managers.Values.ToArray()) @@ -33,13 +38,52 @@ public static IEnumerable GetAll() } } - public event EventHandler? WindowMessage; + public event EventHandler WindowMessage + { + add + { + if (WindowMessageInternal is null) + Attach(); + + WindowMessageInternal += value; + } + remove + { + WindowMessageInternal -= value; + + if (WindowMessageInternal is null) + Detach(); + } + } + + void Attach() + { + lock (_locker) + { + if (_oldWndProc == IntPtr.Zero) + { + _oldWndProc = PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _newWndProc); + } + } + } + + void Detach() + { + lock (_locker) + { + if (_oldWndProc != IntPtr.Zero) + { + PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _oldWndProc); + _oldWndProc = IntPtr.Zero; + } + } + } static IntPtr NewWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam) { if (_managers.TryGetValue(hWnd, out var weakManager) && weakManager.TryGetTarget(out var manager)) { - var evt = manager.WindowMessage; + var evt = manager.WindowMessageInternal; if (evt is not null) { var args = new WindowMessageEventArgs(hWnd, uMsg, wParam, lParam); @@ -87,7 +131,7 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer - PlatformMethods.SetWindowLongPtr(_windowHandle, PlatformMethods.WindowLongFlags.GWL_WNDPROC, _oldWndProc); + Detach(); // set large fields to null diff --git a/src/Essentials/test/DeviceTests/Tests/Windows/ActiveWindowTracker_Tests.cs b/src/Essentials/test/DeviceTests/Tests/Windows/ActiveWindowTracker_Tests.cs new file mode 100644 index 000000000000..784f16955b71 --- /dev/null +++ b/src/Essentials/test/DeviceTests/Tests/Windows/ActiveWindowTracker_Tests.cs @@ -0,0 +1,163 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Platform; +using Xunit; + +namespace Microsoft.Maui.Essentials.DeviceTests.Shared +{ + [Category("Windows ActiveWindowTracker")] + public class Windows_ActiveWindowTracker_Tests : BaseWindowMessageManager_Tests + { + [Fact] + public Task StoppedTrackerDoesNotTrack() => + Utils.OnMainThread(async () => + { + var messages = new List(); + + var window = new UI.Xaml.Window(); + + var wsm = new TestWindowStateManager(); + var tracker = new ActiveWindowTracker(wsm); + tracker.WindowMessage += OnWindowMessage; + + wsm.OnActivated(window); + tracker.Start(); + tracker.Stop(); + + await PostTestMessageAsync(window); + + Assert.DoesNotContain(TEST_MESSAGE, messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); + + [Fact] + public Task ActivatedBeforeStartRecievesMessage() => + Utils.OnMainThread(async () => + { + var messages = new List(); + + var window = new UI.Xaml.Window(); + + var wsm = new TestWindowStateManager(); + var tracker = new ActiveWindowTracker(wsm); + tracker.WindowMessage += OnWindowMessage; + + wsm.OnActivated(window); + tracker.Start(); + + await PostTestMessageAsync(window); + + tracker.Stop(); + + Assert.Contains(TEST_MESSAGE, messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); + + [Fact] + public Task StartBeforeActivatedRecievesMessage() => + Utils.OnMainThread(async () => + { + var messages = new List(); + + var window = new UI.Xaml.Window(); + + var wsm = new TestWindowStateManager(); + wsm.OnActivated(window); + + var tracker = new ActiveWindowTracker(wsm); + tracker.Start(); + tracker.WindowMessage += OnWindowMessage; + + await PostTestMessageAsync(window); + + Assert.Contains(TEST_MESSAGE, messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); + + [Fact] + public Task SwitchingWindowsPostsToTheNewWindow() => + Utils.OnMainThread(async () => + { + var messages = new List<(IntPtr, uint)>(); + + var window1 = new UI.Xaml.Window(); + var window2 = new UI.Xaml.Window(); + + var wsm = new TestWindowStateManager(); + var tracker = new ActiveWindowTracker(wsm); + tracker.Start(); + tracker.WindowMessage += OnWindowMessage; + + wsm.OnActivated(window1); + wsm.OnActivated(window2); + + await PostTestMessageAsync(window2); + + Assert.DoesNotContain((window1.GetWindowHandle(), TEST_MESSAGE), messages); + Assert.Contains((window2.GetWindowHandle(), TEST_MESSAGE), messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add((e.Hwnd, e.MessageId)); + } + }); + + [Fact] + public Task SwitchingWindowsDoesNotPostToTheOldWindow() => + Utils.OnMainThread(async () => + { + var messages = new List<(IntPtr, uint)>(); + + var window1 = new UI.Xaml.Window(); + var window2 = new UI.Xaml.Window(); + + var wsm = new TestWindowStateManager(); + var tracker = new ActiveWindowTracker(wsm); + tracker.Start(); + tracker.WindowMessage += OnWindowMessage; + + wsm.OnActivated(window1); + wsm.OnActivated(window2); + + await PostTestMessageAsync(window1); + + Assert.DoesNotContain((window1.GetWindowHandle(), TEST_MESSAGE), messages); + Assert.DoesNotContain((window2.GetWindowHandle(), TEST_MESSAGE), messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add((e.Hwnd, e.MessageId)); + } + }); + + class TestWindowStateManager : IWindowStateManager + { + UI.Xaml.Window? _window; + + public event EventHandler? ActiveWindowChanged; + + public UI.Xaml.Window? GetActiveWindow() => _window; + + public void OnActivated(UI.Xaml.Window window, UI.Xaml.WindowActivatedEventArgs? args = null) + { + _window = window; + ActiveWindowChanged?.Invoke(window, EventArgs.Empty); + } + } + } +} diff --git a/src/Essentials/test/DeviceTests/Tests/Windows/BaseWindowMessageManager_Tests.cs b/src/Essentials/test/DeviceTests/Tests/Windows/BaseWindowMessageManager_Tests.cs new file mode 100644 index 000000000000..1f78985b34fe --- /dev/null +++ b/src/Essentials/test/DeviceTests/Tests/Windows/BaseWindowMessageManager_Tests.cs @@ -0,0 +1,27 @@ +#nullable enable +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.Maui.Platform; + +namespace Microsoft.Maui.Essentials.DeviceTests.Shared +{ + public abstract class BaseWindowMessageManager_Tests + { + protected const uint WM_APP = 0x8000; + protected const uint TEST_MESSAGE = WM_APP + 1; + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + protected static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + + protected async Task PostTestMessageAsync(UI.Xaml.Window window) + { + var handle = window.GetWindowHandle(); + + PostMessage(handle, TEST_MESSAGE, IntPtr.Zero, IntPtr.Zero); + + await Task.Delay(100); + } + } +} diff --git a/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs b/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs index 43c4f0e088ad..8c327f7af987 100644 --- a/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs +++ b/src/Essentials/test/DeviceTests/Tests/Windows/WindowMessageManager_Tests.cs @@ -1,7 +1,6 @@ -using System; +#nullable enable using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Platform; @@ -10,7 +9,7 @@ namespace Microsoft.Maui.Essentials.DeviceTests.Shared { [Category("Windows WindowMessageManager")] - public class Windows_WindowMessageManager_Tests + public class Windows_WindowMessageManager_Tests : BaseWindowMessageManager_Tests { [Fact] public Task NewWindowIsNotFound() => @@ -37,10 +36,71 @@ public Task RegisteredWindowIsFound() => var allManagers = WindowMessageManager.GetAll().ToArray(); var allHandles = allManagers.Select(m => m.WindowHandle).ToArray(); + manager.Dispose(); + Assert.Contains(handle, allHandles); Assert.Contains(manager, allManagers); }); + [Fact] + public Task RegisteredWindowIsNotAttached() => + Utils.OnMainThread(() => + { + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + + var isAttached = manager.IsAttached; + + manager.Dispose(); + + Assert.False(isAttached); + }); + + [Fact] + public Task SubscribedWindowIsAttached() => + Utils.OnMainThread(() => + { + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + manager.WindowMessage += OnWindowMessage; + + var isAttached = manager.IsAttached; + + manager.Dispose(); + + Assert.True(isAttached); + + static void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + } + }); + + [Fact] + public Task UnsubscribedWindowIsNotAttached() => + Utils.OnMainThread(() => + { + var window = new UI.Xaml.Window(); + var handle = window.GetWindowHandle(); + + var manager = WindowMessageManager.Get(window); + manager.WindowMessage += OnWindowMessage; + manager.WindowMessage -= OnWindowMessage; + + var isAttached = manager.IsAttached; + + manager.Dispose(); + + Assert.False(isAttached); + + static void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + } + }); + [Fact] public Task DisposedManagerIsNotFound() => Utils.OnMainThread(() => @@ -63,22 +123,20 @@ public Task DisposedManagerIsNotFound() => public Task PostingMessagesReachesEvent() => Utils.OnMainThread(async () => { - const uint message = WM_APP + 1; var messages = new List(); var window = new UI.Xaml.Window(); - var handle = window.GetWindowHandle(); var manager = WindowMessageManager.Get(window); manager.WindowMessage += OnWindowMessage; - PostMessage(handle, message, IntPtr.Zero, IntPtr.Zero); + await PostTestMessageAsync(window); - await Task.Delay(100); + manager.Dispose(); - Assert.Contains(message, messages); + Assert.Contains(TEST_MESSAGE, messages); - void OnWindowMessage(object sender, WindowMessageEventArgs e) + void OnWindowMessage(object? sender, WindowMessageEventArgs e) { messages.Add(e.MessageId); } @@ -88,32 +146,71 @@ void OnWindowMessage(object sender, WindowMessageEventArgs e) public Task PostingMessagesToDisposedManagerDoesNotReachEvent() => Utils.OnMainThread(async () => { - const uint message = WM_APP + 1; var messages = new List(); var window = new UI.Xaml.Window(); - var handle = window.GetWindowHandle(); var manager = WindowMessageManager.Get(window); manager.WindowMessage += OnWindowMessage; manager.Dispose(); - PostMessage(handle, message, IntPtr.Zero, IntPtr.Zero); + await PostTestMessageAsync(window); + + Assert.DoesNotContain(TEST_MESSAGE, messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); - await Task.Delay(100); + [Fact] + public Task PostingMessagesToUnsubscribedManagerDoesNotReachEvent() => + Utils.OnMainThread(async () => + { + var messages = new List(); - Assert.DoesNotContain(message, messages); + var window = new UI.Xaml.Window(); + + using var manager = WindowMessageManager.Get(window); + manager.WindowMessage += OnWindowMessage; + manager.WindowMessage -= OnWindowMessage; - void OnWindowMessage(object sender, WindowMessageEventArgs e) + await PostTestMessageAsync(window); + + manager.Dispose(); + + Assert.DoesNotContain(TEST_MESSAGE, messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) { messages.Add(e.MessageId); } }); - const uint WM_APP = 0x8000; + [Fact] + public Task PostingMessagesToMultipleSubscribedManagerReachesEvent() => + Utils.OnMainThread(async () => + { + var messages = new List(); - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + var window = new UI.Xaml.Window(); + + using var manager = WindowMessageManager.Get(window); + manager.WindowMessage += OnWindowMessage; + manager.WindowMessage += OnWindowMessage; + manager.WindowMessage -= OnWindowMessage; + + await PostTestMessageAsync(window); + + manager.Dispose(); + + Assert.Contains(TEST_MESSAGE, messages); + + void OnWindowMessage(object? sender, WindowMessageEventArgs e) + { + messages.Add(e.MessageId); + } + }); } } From 945aca3c0ee68ba046a6cbc3e4904f974146e21d Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Aug 2022 00:09:39 +0200 Subject: [PATCH 7/8] Windows display size is the Screen not Window --- .../Samples/Platforms/Android/MainActivity.cs | 2 +- .../samples/Samples/View/DeviceInfoPage.xaml | 9 +- .../Samples/ViewModel/DeviceInfoViewModel.cs | 12 +- .../src/DeviceDisplay/DeviceDisplay.uwp.cs | 105 +++++------------- 4 files changed, 45 insertions(+), 83 deletions(-) diff --git a/src/Essentials/samples/Samples/Platforms/Android/MainActivity.cs b/src/Essentials/samples/Samples/Platforms/Android/MainActivity.cs index 2b12a3e04236..c2140a85ae5b 100644 --- a/src/Essentials/samples/Samples/Platforms/Android/MainActivity.cs +++ b/src/Essentials/samples/Samples/Platforms/Android/MainActivity.cs @@ -8,7 +8,7 @@ namespace Samples.Droid { - [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] + [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.FullSensor)] [IntentFilter( new[] { Microsoft.Maui.ApplicationModel.Platform.Intent.ActionAppAction }, Categories = new[] { Intent.CategoryDefault })] diff --git a/src/Essentials/samples/Samples/View/DeviceInfoPage.xaml b/src/Essentials/samples/Samples/View/DeviceInfoPage.xaml index 969b03dcaa5b..d82cf3423259 100644 --- a/src/Essentials/samples/Samples/View/DeviceInfoPage.xaml +++ b/src/Essentials/samples/Samples/View/DeviceInfoPage.xaml @@ -8,10 +8,10 @@ - + + \ No newline at end of file diff --git a/src/Essentials/samples/Samples/ViewModel/DeviceInfoViewModel.cs b/src/Essentials/samples/Samples/ViewModel/DeviceInfoViewModel.cs index f0f4fd65ff81..b46d746f701b 100644 --- a/src/Essentials/samples/Samples/ViewModel/DeviceInfoViewModel.cs +++ b/src/Essentials/samples/Samples/ViewModel/DeviceInfoViewModel.cs @@ -21,11 +21,21 @@ public class DeviceInfoViewModel : BaseViewModel public DeviceIdiom Idiom => DeviceInfo.Idiom; public DeviceType DeviceType => DeviceInfo.DeviceType; + + public double Rotation => + ScreenMetrics.Rotation switch + { + DisplayRotation.Rotation0 => 0, + DisplayRotation.Rotation90 => -90, + DisplayRotation.Rotation180 => -180, + DisplayRotation.Rotation270 => -270, + _ => 0 + }; public DisplayInfo ScreenMetrics { get => screenMetrics; - set => SetProperty(ref screenMetrics, value); + set => SetProperty(ref screenMetrics, value, onChanged: () => OnPropertyChanged(nameof(Rotation))); } public override void OnAppearing() diff --git a/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs b/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs index 9161e1a872dd..439207ab29a7 100644 --- a/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs +++ b/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs @@ -44,40 +44,6 @@ protected override void SetKeepScreenOn(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; - } - AppWindow? _currentAppWindowListeningTo; protected override DisplayInfo GetMainDisplayInfo() @@ -91,14 +57,9 @@ protected override DisplayInfo GetMainDisplayInfo() if (mi == null) return new DisplayInfo(); - DEVMODE vDevMode = new DEVMODE(); + var 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; @@ -108,13 +69,18 @@ protected override DisplayInfo GetMainDisplayInfo() else dpi = 1.0; - var orientation = GetWindowOrientationWin32(appWindow) == DisplayOrientations.Landscape + var displayOrientation = GetDisplayOrientation(vDevMode); + var rotation = CalculateRotation(displayOrientation); + + var orientation = displayOrientation == DisplayOrientations.Landscape || displayOrientation == DisplayOrientations.LandscapeFlipped ? DisplayOrientation.Landscape : DisplayOrientation.Portrait; + var perpendicular = orientation == DisplayOrientation.Portrait; + return new DisplayInfo( - width: perpendicular ? h : w, - height: perpendicular ? w : h, + width: w, + height: h, density: dpi, orientation: orientation, rotation: rotation, @@ -179,42 +145,25 @@ void OnWindowDisplayChanged(object? sender, EventArgs e) => void OnAppWindowChanged(AppWindow sender, AppWindowChangedEventArgs args) => OnMainDisplayInfoChanged(); - static DisplayRotation CalculateRotation(DEVMODE devMode, AppWindow appWindow) - { - DisplayOrientations native = DisplayOrientations.Portrait; - switch (devMode.dmDisplayOrientation) + static DisplayRotation CalculateRotation(DisplayOrientations orientation) => + orientation switch { - 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; - } + 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", CharSet = CharSet.Unicode)] static extern int GetDpiForWindow(IntPtr hwnd); From fc27a3e315eccc47df47db9a2ba15a81a5b485df Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Aug 2022 00:13:03 +0200 Subject: [PATCH 8/8] Clean up unused externs --- .../src/DeviceDisplay/DeviceDisplay.uwp.cs | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs b/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs index 439207ab29a7..6bdd06d9adcf 100644 --- a/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs +++ b/src/Essentials/src/DeviceDisplay/DeviceDisplay.uwp.cs @@ -49,13 +49,13 @@ protected override void SetKeepScreenOn(bool keepScreenOn) protected override DisplayInfo GetMainDisplayInfo() { if (WindowStateManager.Default.GetActiveAppWindow(false) is not AppWindow appWindow) - return new DisplayInfo(); + return default; var windowHandle = UI.Win32Interop.GetWindowFromWindowId(appWindow.Id); var mi = GetDisplay(windowHandle); if (mi == null) - return new DisplayInfo(); + return default; var vDevMode = new DEVMODE(); EnumDisplaySettings(mi.Value.DeviceNameToLPTStr(), -1, ref vDevMode); @@ -76,8 +76,6 @@ protected override DisplayInfo GetMainDisplayInfo() ? DisplayOrientation.Landscape : DisplayOrientation.Portrait; - var perpendicular = orientation == DisplayOrientation.Portrait; - return new DisplayInfo( width: w, height: h, @@ -165,19 +163,19 @@ static DisplayOrientations GetDisplayOrientation(DEVMODE devMode) => _ => DisplayOrientations.Landscape, }; - [DllImport("User32", CharSet = CharSet.Unicode)] + [DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int GetDpiForWindow(IntPtr hwnd); - [DllImport("User32", CharSet = CharSet.Unicode, SetLastError = true)] + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr MonitorFromRect(ref RECT lprc, MonitorOptions dwFlags); - [DllImport("User32", CharSet = CharSet.Unicode, SetLastError = true)] + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); - [DllImport("User32.dll")] + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - static extern Boolean EnumDisplaySettings( + static extern bool EnumDisplaySettings( byte[] lpszDeviceName, [param: MarshalAs(UnmanagedType.U4)] int iModeNum, [In, Out] ref DEVMODE lpDevMode); @@ -185,20 +183,6 @@ static extern Boolean EnumDisplaySettings( [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, @@ -241,9 +225,10 @@ public byte[] DeviceNameToLPTStr() struct DEVMODE { - private const int CCHDEVICENAME = 0x20; - private const int CCHFORMNAME = 0x20; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + const int CCHDEVICENAME = 0x20; + const int CCHFORMNAME = 0x20; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] public string dmDeviceName; public short dmSpecVersion; public short dmDriverVersion; @@ -259,7 +244,7 @@ struct DEVMODE public short dmYResolution; public short dmTTOption; public short dmCollate; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)] public string dmFormName; public short dmLogPixels; public int dmBitsPerPel;