|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
//
using System;
using System.Diagnostics;
using System.Windows.Threading;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Peers;
using System.Windows.Media.Composition;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using MS.Internal;
using MS.Internal.Automation;
using MS.Internal.Interop;
using MS.Utility;
using MS.Win32;
using MS.Internal.PresentationCore; // SecurityHelper
using SR=MS.Internal.PresentationCore.SR;
using HRESULT = MS.Internal.HRESULT;
using NativeMethodsSetLastError = MS.Internal.WindowsBase.NativeMethodsSetLastError;
using PROCESS_DPI_AWARENESS = MS.Win32.NativeMethods.PROCESS_DPI_AWARENESS;
#pragma warning disable 1634, 1691 // suppressing PreSharp warnings
namespace System.Windows.Interop
{
// This is the internal, more expressive, enum used by the InvalidateRenderMode method.
// See the RenderMode enum and the RenderMode property for the public version.
internal enum RenderingMode
{
Default = MILRTInitializationFlags.MIL_RT_INITIALIZE_DEFAULT,
Software = MILRTInitializationFlags.MIL_RT_SOFTWARE_ONLY,
Hardware = MILRTInitializationFlags.MIL_RT_HARDWARE_ONLY,
HardwareReference = MILRTInitializationFlags.MIL_RT_HARDWARE_ONLY | MILRTInitializationFlags.MIL_RT_USE_REF_RAST,
DisableMultimonDisplayClipping = MILRTInitializationFlags.MIL_RT_DISABLE_MULTIMON_DISPLAY_CLIPPING,
IsDisableMultimonDisplayClippingValid = MILRTInitializationFlags.MIL_RT_IS_DISABLE_MULTIMON_DISPLAY_CLIPPING_VALID,
DisableDirtyRectangles = MILRTInitializationFlags.MIL_RT_DISABLE_DIRTY_RECTANGLES,
}
// This is the public, more limited, enum exposed for use with the RenderMode property.
// See the RenderingMode enum and InvalidateRenderMode method for the internal version.
/// <summary>
/// Render mode preference.
/// </summary>
public enum RenderMode
{
/// <summary>
/// The rendering layer should use the GPU and CPU as appropriate.
/// </summary>
Default,
/// <summary>
/// The rendering layer should only use the CPU.
/// </summary>
SoftwareOnly
}
/// <summary>
/// The HwndTarget class represents a binding to an HWND.
/// </summary>
/// <remarks>
/// The HwndTarget is not thread-safe. Accessing the HwndTarget from a different
/// thread than it was created will throw a <see cref="System.InvalidOperationException"/>.
///
/// All value-type statics in this class that depend on the initial HWND for initialization - notably
/// those related to DPI like <see cref="ProcessDpiAwareness"/> - must be represented as a
/// nullable. This ensures that callers have a clear understanding of whether these have
/// been initialized or not.
/// </remarks>
public class HwndTarget : CompositionTarget
{
/// <summary>
/// Lock object used to ensure that initialization of other statics
/// happens race-free
/// </summary>
private static readonly object s_lockObject = new object();
private static WindowMessage s_updateWindowSettings;
private static WindowMessage s_needsRePresentOnWake;
/// <summary>
/// wpfgfx will raise this message whenever a new display-set is enumerated.
/// wParam: 1 if valid displays are available, 0 otherwise
/// lParam: Not used
/// </summary>
private static WindowMessage s_DisplayDevicesAvailabilityChanged;
/// <summary>
/// This is returned by <see cref="HandleMessage(WindowMessage, IntPtr, IntPtr)"/>
/// when a Window message handled exclusively by it.
/// </summary>
private static readonly IntPtr Handled = new IntPtr(0x1);
/// <summary>
/// This is returned by <see cref="HandleMessage(WindowMessage, IntPtr, IntPtr)"/>
/// when other Window procs should be allowed to continue processing a
/// given Window message - i.e., <see cref="HandleMessage(WindowMessage, IntPtr, IntPtr)"/>
/// does not process that Window message exclusively
/// </summary>
private static readonly IntPtr Unhandled = IntPtr.Zero;
private MatrixTransform _worldTransform;
private RenderMode _renderModePreference = RenderMode.Default;
private NativeMethods.HWND _hWnd;
private NativeMethods.RECT _hwndClientRectInScreenCoords = new NativeMethods.RECT();
private NativeMethods.RECT _hwndWindowRectInScreenCoords = new NativeMethods.RECT();
private Color _backgroundColor = Color.FromRgb(0, 0, 0);
private DUCE.MultiChannelResource _compositionTarget =
new DUCE.MultiChannelResource();
private bool _isRenderTargetEnabled = true;
// private Nullable<Color> _colorKey = null;
// private double _opacity = 1.0;
private bool _usesPerPixelOpacity = false;
// It is important that this start at zero to allow an initial
// UpdateWindowSettings(enable) command to enable the render target
// without a preceeding UpdateWindowSettings(disable) command.
private int _disableCookie = 0;
// Used to deal with layered window problems. See comments where they are used.
private bool _isMinimized = false;
private bool _isSessionDisconnected = false;
private bool _isSuspended = false;
// True when user input is causing a resize. We use this to determine whether or
// not we want to sync during resize to provide a better looking resize.
private bool _userInputResize = false;
// This bool is set by a private window message sent to us from the render thread,
// indicating that the present has failed with S_PRESENT_OCCLUDED (usually due to the
// monitor being asleep or locked) and that we need to invalidate the entire window for
// presenting when the monitor turns back on.
private bool _needsRePresentOnWake = false;
// See comment above for _needsRePresentOnWake. If the present has failed because of a
// reason other than the monitor being asleep (usually because a D3D full screen exclusive
// app is occluding the WPF app), we need to be able to recognize this situation and avoid
// continually invalidating the window and causing presents that will fail and continue the
// cycle (the so called "WM_PAINT storm"). We set this member to true the first time we
// invalidate due to the private window message indicating failure if we are *not* asleep, once
// the timeout period specified by _allowedPresentFailureDelay has passed
// Any failure after that until another sleep state event occurs will not trigger an invalidate.
private bool _hasRePresentedSinceWake = false;
/// <summary>
/// True if wpfgfx indicates that valid displays
/// are available. This is communiated by use of the
/// window message <see cref="s_DisplayDevicesAvailabilityChanged"/>
/// </summary>
/// <remarks>
/// Normally, we'd want to initialize this to true when we are running in a
/// Window Station in interactive mode (WinSta0), which is typical for desktop applications.
/// On the other hand, we'd want to initialize this to false when running in a
/// non-interactive Window Station (for e.g., typical SCM services). This can be
/// identified by <see cref="Environment.UserInteractive"/>.
///
/// Instead of initializing it this way directly, we instead initialize <see cref="_displayDevicesAvailable"/>
/// using <see cref="MediaContext.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable"/>, which in turn factors in (a)
/// <see cref="Environment.UserInteractive"/>, and (b) a registry override that requests
/// that WPF's renderer act as if interactive displays are always present
/// even when displays aren't - either because the process is running in an
/// non-interactive Window Station, or because the session is in a
/// <see cref="NativeMethods.WTS_CONNECTSTATE_CLASS.WTSDisconnected"/> state, and (c) an compat
/// override that can be set in the application configuration file (app.config)
/// </remarks>
private bool _displayDevicesAvailable = MediaContext.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable;
/// <summary>
/// True if WM_PAINT processing was deferred due to
/// <see cref="_displayDevicesAvailable"/> being false.
///
/// We will use this flag to determine whether we need to
/// invalidate the entire window when display devices become
/// available
/// </summary>
private bool _wasWmPaintProcessingDeferred = false;
/// <summary>
/// Session ID of this process
/// </summary>
/// <remarks>
/// If the query for the session ID using WTS API's fails,
/// then this value will remain null
/// </remarks>
private int? _sessionId = null;
// The time of the last wake or unlock message we received. When we receive a lock/sleep message,
// we set this value to DateTime.MinValue
private DateTime _lastWakeOrUnlockEvent;
// This is the amount of time we continue to propagate Invalidate() calls when we receive notifications
// from the render thread that present has failed, as measured from the value of _lastWakeOrUnlockEvent.
// This allows for a window of time during which we have received the, for eg, session unlock message,
// but the D3D device is still returning S_PRESENT_OCCLUDED. Time is in seconds.
private const double _allowedPresentFailureDelay = 10.0;
private DispatcherTimer _restoreDT;
/// <summary>
/// Initializes static variables for this class.
/// </summary>
static HwndTarget()
{
s_updateWindowSettings = UnsafeNativeMethods.RegisterWindowMessage("UpdateWindowSettings");
s_needsRePresentOnWake = UnsafeNativeMethods.RegisterWindowMessage("NeedsRePresentOnWake");
s_DisplayDevicesAvailabilityChanged =
UnsafeNativeMethods.RegisterWindowMessage("DisplayDevicesAvailabilityChanged");
}
/// <summary>
/// Attaches a hwndTarget to the hWnd
/// <remarks>
/// This API link demands for UIWindowPermission.AllWindows
/// </remarks>
/// </summary>
/// <param name="hwnd">The HWND to which the HwndTarget will draw.</param>
/// <remarks>
/// Callers must have UIPermission(UIPermissionWindow.AllWindows) to call this API.
/// </remarks>
public HwndTarget(IntPtr hwnd)
{
bool exceptionThrown = true;
_sessionId = SafeNativeMethods.GetCurrentSessionId();
_isSessionDisconnected = !SafeNativeMethods.IsCurrentSessionConnectStateWTSActive(_sessionId);
if (_isSessionDisconnected)
{
_needsRePresentOnWake = true;
}
AttachToHwnd(hwnd);
try
{
if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info))
{
EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientCreateVisual, EventTrace.Keyword.KeywordGeneral, EventTrace.Level.Info, Dispatcher.GetHashCode(), hwnd.ToInt64());
}
_hWnd = NativeMethods.HWND.Cast(hwnd);
// Get the client rectangle...
UpdateWindowAndClientCoordinates();
_lastWakeOrUnlockEvent = DateTime.MinValue;
// Get the Process and window DPI awareness, and System
// and Window DPI scale factors
//
// Initialize statics (done exactly once per process)
// PROCESS_DPI_AWARENESS values
// ProcessDpiAwareness property
// ActualProcessDpiAwareness property
// system DPI scale
// Initialize per-HwndTarget values (done once per HwndTarget instance)
// DPI_AWARENESS_CONTEXT (DpiAwarenessContext property)
// Window DPI scale (CurrentDpiScale field)
InitializeDpiAwarenessAndDpiScales();
CheckAndDisableSpecialCharacterLigature();
_worldTransform = new MatrixTransform(
new Matrix(
CurrentDpiScale.DpiScaleX, 0,
0 , CurrentDpiScale.DpiScaleY,
0 , 0));
//
// Register CompositionTarget with MediaContext.
//
MediaContext.RegisterICompositionTarget(Dispatcher, this);
// Initialize dispatcher timer to work-around a restore issue.
_restoreDT = new DispatcherTimer();
_restoreDT.Tick += new EventHandler(InvalidateSelf);
_restoreDT.Interval = TimeSpan.FromMilliseconds(100);
exceptionThrown = false;
}
finally
{
//
// If exception has occurred after we attached this target to
// the window, we need to detach from this window. Otherwise, window
// will be left in a state when no other HwndTarget can be created
// for it.
//
if(exceptionThrown)
{
#pragma warning suppress 6031 // Return value ignored on purpose.
VisualTarget_DetachFromHwnd(hwnd);
}
}
}
/// <summary>
/// Disables hyphen ligatures if user has exlicitly wants it
/// </summary>
private void CheckAndDisableSpecialCharacterLigature()
{
NativeMethodsSetLastError.LsDisableSpecialCharacterLigature(CoreAppContextSwitches.DisableSpecialCharacterLigature);
}
/// <summary>
/// Ensures the system/primary monitor's DPI scale. Get the process DPI awareness.
/// </summary>
/// <remarks>Helper for constructor</remarks>
private void InitializeDpiAwarenessAndDpiScales()
{
// Only do this once to get:
// 1. Process DPI Awareness
// 2. System/Primary monitor's DPI, store it in the first entry of the static array UIElement::MonitorDPIScaleX/Y.
// Primary Monitor's DPI is needed in two cases :
// i) When process is System DPI Aware, then we draw it as per system DPI.
// ii) As a fallback value in case of failure.
lock (s_lockObject)
{
if (!AppManifestProcessDpiAwareness.HasValue)
{
PROCESS_DPI_AWARENESS appManifestProcessDpiAwareness;
PROCESS_DPI_AWARENESS processDpiAwareness;
GetProcessDpiAwareness(_hWnd, out appManifestProcessDpiAwareness, out processDpiAwareness);
AppManifestProcessDpiAwareness = appManifestProcessDpiAwareness;
ProcessDpiAwareness = processDpiAwareness;
DpiUtil.UpdateUIElementCacheForSystemDpi(DpiUtil.GetSystemDpi());
}
}
// Initialize DpiAwarenessContext (DPI_AWARENESS_CONTEXT) every
// time the HwndTarget constructor runs- this can change for each HWND
DpiAwarenessContext = (DpiAwarenessContextValue)DpiUtil.GetDpiAwarenessContext(_hWnd);
CurrentDpiScale = GetDpiScaleForWindow(_hWnd);
}
/// <summary>
/// Obtains the DPI awareness of the first process from
/// which an HWND is used to instantiate an <see cref="HwndTarget"/>.
/// In most cases, this is same process as the WPF application itself.
/// </summary>
/// <remarks>
/// Helper for <see cref="InitializeDpiAwarenessAndDpiScales"/> which in turn
/// is a helper for constructor.
///
/// This can't be done in the static constructor due to the dependence
/// on the HWND being passed through the instance constructor
///
/// Note that all statics that depend on the inital HWND for intialization
/// must be represented as a nullable value.
/// </remarks>
private static void GetProcessDpiAwareness(
IntPtr hWnd,
out PROCESS_DPI_AWARENESS appManifestProcessDpiAwareness,
out PROCESS_DPI_AWARENESS processDpiAwareness)
{
// 1. Initialize (static) AppManifestProcessDpiAwareness
// 2. Initalize (static) ProcessDpiAwareness
appManifestProcessDpiAwareness = DpiUtil.GetProcessDpiAwareness(hWnd);
// Don't check for AppContext flag here. We just want to
// inventory process characteristics here
if (IsPerMonitorDpiScalingEnabled)
{
processDpiAwareness = appManifestProcessDpiAwareness;
}
else
{
// 'legacy' values can either be 'system aware' or 'unaware'
processDpiAwareness = DpiUtil.GetLegacyProcessDpiAwareness();
}
}
/// <summary>
/// If process is Per Monitor DPI aware, returns the DPI scale factor of
/// the window - which is typically also the DPI scale factor
/// of the monitor on which the <paramref name="hWnd"/> is displayed.
/// Otherwise returns the system DPI.
/// </summary>
/// <remarks>
/// We used to identify the DPI of an HWND by doing the following:
/// int dpiX, dpiY;
///
/// var hMon = User32!MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)
/// shcore!GetDpiForMonitor(hMon, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out dpiX, out dpiY);
///
/// On Windows desktop, DPI scale factor on X and Y axis are equal (i.e., dpiX == dpiY), and
/// Windows provides a simpler API to obtain this value as:
/// var dpi = dpiX = dpiY = user32!GetDpiForWindow(hwnd);
///
/// The nice thing about the GetDpiForWindow API is that it will work correctly when dealing
/// with mixed mode DPI scenarios, when the process might be per-monitor DPI aware, yet
/// an individual HWND might be system-aware or unaware, resulting in a DPI scale factor
/// that is different than that of the nearest monitor.
///
/// In other words, in the few places scenarios that user32!GetDpiForWindow generates different
/// results compard to the older approach, it enhances our implementation and offers implict
/// bug fixes.
/// </remarks>
private static DpiScale2 GetDpiScaleForWindow(IntPtr hWnd)
{
DpiScale2 dpiScale = null;
if (IsPerMonitorDpiScalingEnabled)
{
// When IsPerMonitorDpiScalignEnabled==true, it just means that we are
// running on OS >= Windows 10 RS2, and the application has not requested
// (via an AppContext switch) turning off of WPF's High DPI logic, if relevant
// Under these circumstances, we can simply rely upon the DPI information
// reported by the HWND itself, irrespective of whether the HWND is Per-Monitor aware,
// System-Aware, or Unaware.
dpiScale = DpiUtil.GetWindowDpi(hWnd, fallbackToNearestMonitorHeuristic: false);
}
else if (ProcessDpiAwareness.HasValue)
{
if (IsProcessSystemAware == true)
{
dpiScale = DpiUtil.GetSystemDpiFromUIElementCache();
}
else if (IsProcessUnaware == true)
{
dpiScale = DpiScale2.FromPixelsPerInch(DpiUtil.DefaultPixelsPerInch, DpiUtil.DefaultPixelsPerInch);
}
}
if (dpiScale == null)
{
// The Window DPI could not be found likely because HwndTarget statics have not
// been initialized yet. Fall back to legacy logic.
var dpiAwareness = DpiUtil.GetLegacyProcessDpiAwareness();
switch (dpiAwareness)
{
case PROCESS_DPI_AWARENESS.PROCESS_SYSTEM_DPI_AWARE:
dpiScale = DpiUtil.GetSystemDpi();
break;
case PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE:
dpiScale = IsPerMonitorDpiScalingEnabled
? DpiUtil.GetWindowDpi(hWnd, fallbackToNearestMonitorHeuristic: false)
: DpiUtil.GetSystemDpi();
break;
case PROCESS_DPI_AWARENESS.PROCESS_DPI_UNAWARE:
default:
dpiScale = DpiScale2.FromPixelsPerInch(
DpiUtil.DefaultPixelsPerInch,
DpiUtil.DefaultPixelsPerInch);
break;
}
}
return dpiScale;
}
/// <summary>
/// We will use the parent HWND of WS_POPUP windows
/// to determine the DPI. There can be a popup
/// within a popup, so we obtain the top-level HWND parent.
/// </summary>
/// <remarks>
/// In the past, we used to treat WS_CHILD windows similar to
/// WS_POPUP windows, and looked to the top-level HWND to determine
/// the DPI. We don't do so anymore given child-level mixed mode
/// DPI is possible in Windows.
///
/// We might consider rendering popup's at their native DPI's as well,
/// eventually
/// </remarks>
private static HandleRef NormalizeWindow(HandleRef hWnd, bool normalizeChildWindows, bool normalizePopups)
{
HandleRef normalizedHwnd = hWnd;
Debug.Assert(normalizedHwnd.Handle != IntPtr.Zero);
object wrapperObject = hWnd.Wrapper;
int dwMask =
(normalizeChildWindows ? NativeMethods.WS_CHILD : 0) |
(normalizePopups ? NativeMethods.WS_POPUP : 0);
int style = NativeMethods.IntPtrToInt32((IntPtr)SafeNativeMethods.GetWindowStyle(hWnd, false));
if ((style & dwMask) != 0)
{
IntPtr hwndParent = IntPtr.Zero;
do
{
try
{
hwndParent = UnsafeNativeMethods.GetParent(normalizedHwnd);
}
// Call to GetParent can throw an exception in the following scenarios:
// 1) The window is a top - level window that is unowned or does not have the WS_POPUP style.
// 2) The owner window has WS_POPUP style.
// In either of the situations, the right thing to do is obtain the owner and let the loop continue.
catch (Win32Exception)
{
hwndParent = UnsafeNativeMethods.GetWindow(normalizedHwnd, NativeMethods.GW_OWNER);
}
if (hwndParent != IntPtr.Zero)
{
normalizedHwnd = new HandleRef(wrapperObject, hwndParent);
}
} while (hwndParent != IntPtr.Zero);
}
Debug.Assert(normalizedHwnd.Handle != IntPtr.Zero);
return normalizedHwnd;
}
/// <summary>
/// AttachToHwnd
/// </summary>
private void AttachToHwnd(IntPtr hwnd)
{
int processId = 0;
int threadId = UnsafeNativeMethods.GetWindowThreadProcessId(
new HandleRef(this, hwnd),
out processId
);
if (!UnsafeNativeMethods.IsWindow(new HandleRef(this, hwnd)))
{
throw new ArgumentException(
SR.HwndTarget_InvalidWindowHandle,
"hwnd"
);
}
else if (processId != Environment.ProcessId)
{
throw new ArgumentException(
SR.HwndTarget_InvalidWindowProcess,
"hwnd"
);
}
else if (threadId != SafeNativeMethods.GetCurrentThreadId())
{
throw new ArgumentException(
SR.HwndTarget_InvalidWindowThread,
"hwnd"
);
}
int hr = VisualTarget_AttachToHwnd(hwnd);
if (HRESULT.Failed(hr))
{
if (hr == unchecked((int)0x80070005)) // E_ACCESSDENIED
{
throw new InvalidOperationException(
SR.HwndTarget_WindowAlreadyHasContent
);
}
else
{
HRESULT.Check(hr);
}
}
EnsureNotificationWindow();
_notificationWindowHelper.AttachHwndTarget(this);
UnsafeNativeMethods.WTSRegisterSessionNotification(hwnd, NativeMethods.NOTIFY_FOR_THIS_SESSION);
}
[DllImport(DllImport.MilCore, EntryPoint = "MilVisualTarget_AttachToHwnd")]
internal static extern int VisualTarget_AttachToHwnd(
IntPtr hwnd
);
[DllImport(DllImport.MilCore, EntryPoint = "MilVisualTarget_DetachFromHwnd")]
internal static extern int VisualTarget_DetachFromHwnd(
IntPtr hwnd
);
internal void InvalidateRenderMode()
{
RenderingMode mode =
RenderMode == RenderMode.SoftwareOnly ? RenderingMode.Software : RenderingMode.Default;
//
// If ForceSoftwareRendering is set then the transport is connected to a client (magnifier) that cannot
// handle our transport protocol version. Therefore we force software rendering so that the rendered
// content is available through NTUser redirection. If software is not allowed an exception is thrown.
//
if (MediaSystem.ForceSoftwareRendering)
{
if (mode == RenderingMode.Hardware ||
mode == RenderingMode.HardwareReference)
{
throw new InvalidOperationException(SR.HwndTarget_HardwareNotSupportDueToProtocolMismatch);
}
else
{
Debug.Assert(mode == RenderingMode.Software || mode == RenderingMode.Default);
// If the mode is default we can chose what works. When we have a mismatched transport protocol version
// we need to fallback to software rendering.
mode = RenderingMode.Software;
}
}
//Obtain compatibility flags set in the application
bool? enableMultiMonitorDisplayClipping =
System.Windows.CoreCompatibilityPreferences.EnableMultiMonitorDisplayClipping;
if (enableMultiMonitorDisplayClipping != null)
{
// The flag is explicitly set by the user in application manifest
mode |= RenderingMode.IsDisableMultimonDisplayClippingValid;
if (!enableMultiMonitorDisplayClipping.Value)
{
mode |= RenderingMode.DisableMultimonDisplayClipping;
}
}
if (MediaSystem.DisableDirtyRectangles)
{
mode |= RenderingMode.DisableDirtyRectangles;
}
// Select the render target initialization flags based on the requested
// rendering mode.
DUCE.ChannelSet channelSet = MediaContext.From(Dispatcher).GetChannels();
DUCE.Channel channel = channelSet.Channel;
DUCE.CompositionTarget.SetRenderingMode(
_compositionTarget.GetHandle(channel),
(MILRTInitializationFlags)mode,
channel);
}
/// <summary>
/// Specifies the render mode preference for the window.
/// </summary>
/// <remarks>
/// This property specifies a preference, it does not necessarily change the actual
/// rendering mode. Among other things, this can be trumped by the registry settings.
/// <para/>
/// Callers must have UIPermission(UIPermissionWindow.AllWindows) to set this property.
/// </remarks>
public RenderMode RenderMode
{
get
{
return _renderModePreference;
}
// Note: We think it is safe to expose this in partial trust, but doing so would suggest
// we should also expose HwndSource (the only way to get to the HwndTarget instance).
// We don't want to bite off that much exposure at this point in the product, so we enforce
// that this is not accessible from partial trust for now.
set
{
if (value != RenderMode.Default && value != RenderMode.SoftwareOnly)
{
throw new System.ComponentModel.InvalidEnumArgumentException("value", (int)value, typeof(RenderMode));
}
_renderModePreference = value;
InvalidateRenderMode();
}
}
/// <summary>
/// Dispose cleans up the state associated with HwndTarget.
/// </summary>
public override void Dispose()
{
// Its outside the try finally block because we want the exception to be
// thrown if we are on a different thread and we don't want to call Dispose
// on base class in that case.
VerifyAccess();
try
{
// According to spec: Dispose should not raise exception if called multiple times.
// This test is needed because the HwndTarget is Disposed from both the media contex and
// the hwndsrc.
if (!IsDisposed)
{
RootVisual = null;
HRESULT.Check(VisualTarget_DetachFromHwnd(_hWnd));
//
// Unregister this CompositionTarget from the MediaSystem.
//
MediaContext.UnregisterICompositionTarget(Dispatcher, this);
if (_notificationWindowHelper != null &&
_notificationWindowHelper.DetachHwndTarget(this))
{
_notificationWindowHelper.Dispose();
_notificationWindowHelper = null;
}
// Unregister for Fast User Switching messages
UnsafeNativeMethods.WTSUnRegisterSessionNotification(_hWnd);
}
}
finally
{
base.Dispose();
GC.SuppressFinalize(this);
}
}
/// <summary>
/// This method is used to create all uce resources either on Startup or session connect
/// </summary>
internal override void CreateUCEResources(DUCE.Channel channel, DUCE.Channel outOfBandChannel)
{
// create visual target resources
// this forces the creation of the media context if we don't already have one.
base.CreateUCEResources(channel, outOfBandChannel);
Debug.Assert(!_compositionTarget.IsOnChannel(channel));
Debug.Assert(!_compositionTarget.IsOnChannel(outOfBandChannel));
//
// For each HwndTarget we are building some structures in the UCE.
// This includes spinning up a UCE render target. We need to commit the
// batch for those changes right away, since we need to be able to process
// the invalidate packages that we send down on WM_PAINTs. If we don't commit
// right away a WM_PAINT can get fired before we get a chance to commit
// the batch.
//
//
// First we create the composition target, composition context, and the composition root node.
// Note, that composition target will be created out of band because invalidate
// command is also sent out of band and that can occur before current channel is committed.
// We would like to avoid commiting channel here to prevent visual artifacts.
//
bool resourceCreated = _compositionTarget.CreateOrAddRefOnChannel(this, outOfBandChannel, DUCE.ResourceType.TYPE_HWNDRENDERTARGET);
Debug.Assert(resourceCreated);
_compositionTarget.DuplicateHandle(outOfBandChannel, channel);
outOfBandChannel.CloseBatch();
outOfBandChannel.Commit();
DUCE.CompositionTarget.HwndInitialize(
_compositionTarget.GetHandle(channel),
_hWnd,
_hwndClientRectInScreenCoords.right - _hwndClientRectInScreenCoords.left,
_hwndClientRectInScreenCoords.bottom - _hwndClientRectInScreenCoords.top,
MediaSystem.ForceSoftwareRendering,
(int)DpiAwarenessContext,
CurrentDpiScale,
channel
);
DUCE.ResourceHandle hWorldTransform = ((DUCE.IResource)_worldTransform).AddRefOnChannel(channel);
DUCE.CompositionNode.SetTransform(
_contentRoot.GetHandle(channel),
hWorldTransform,
channel);
DUCE.CompositionTarget.SetClearColor(
_compositionTarget.GetHandle(channel),
_backgroundColor,
channel);
//
// Set initial state on the visual target.
//
Rect clientRect = new Rect(
0,
0,
(float)(Math.Ceiling((double)(_hwndClientRectInScreenCoords.right - _hwndClientRectInScreenCoords.left))),
(float)(Math.Ceiling((double)(_hwndClientRectInScreenCoords.bottom - _hwndClientRectInScreenCoords.top))));
StateChangedCallback(
new object[]
{
HostStateFlags.WorldTransform |
HostStateFlags.ClipBounds,
_worldTransform.Matrix,
clientRect
});
DUCE.CompositionTarget.SetRoot(
_compositionTarget.GetHandle(channel),
_contentRoot.GetHandle(channel),
channel);
// reset the disable cookie when creating the slave resource. This happens when creating the
// managed resource and on handling a connect.
_disableCookie = 0;
//
// Finally, update window settings to reflect the state of this object.
// Because CreateUCEResources is called for each channel, only call
// UpdateWindowSettings on that channel this time.
//
DUCE.ChannelSet channelSet;
channelSet.Channel = channel;
channelSet.OutOfBandChannel = outOfBandChannel;
UpdateWindowSettings(_isRenderTargetEnabled, channelSet);
}
/// <summary>
/// This method is used to release all uce resources either on Shutdown or session disconnect
/// </summary>
internal override void ReleaseUCEResources(DUCE.Channel channel, DUCE.Channel outOfBandChannel)
{
if (_compositionTarget.IsOnChannel(channel))
{
//
// If we need to flush the batch we need to render first all visual targets that
// are still registered with the MediaContext to avoid strutural tearing.
// Set the composition target root node to null.
DUCE.CompositionTarget.SetRoot(
_compositionTarget.GetHandle(channel),
DUCE.ResourceHandle.Null,
channel);
_compositionTarget.ReleaseOnChannel(channel);
}
if (_compositionTarget.IsOnChannel(outOfBandChannel))
{
_compositionTarget.ReleaseOnChannel(outOfBandChannel);
}
DUCE.ResourceHandle hWorldTransform = ((DUCE.IResource)_worldTransform).GetHandle(channel);
if (!hWorldTransform.IsNull)
{
// Release the world transform from this channel if it's currently on the channel.
((DUCE.IResource)_worldTransform).ReleaseOnChannel(channel);
}
// release all the visual target resources.
base.ReleaseUCEResources(channel, outOfBandChannel);
}
/// <summary>
/// Handler for WM_DPICHANGED message
/// </summary>
/// <param name="wParam">
/// The HIWORD of the wParam contains the Y-axis value of the new dpi of the window.
/// the LOWORD of the wParam contains the X-axis value of the new DPI of the
/// window. For example, 96, 120, 144, or 192. The values of the X-axis and Y-axis are identical
/// for Windows apps.
/// </param>
/// <param name="lParam">
/// Contains pointer to a RECT structure that provides the suggested
/// size and position of the current window scaled for the new DPI. The expectation
/// is that apps will reposition and resize the windows based on the suggestions
/// provided by lParam when handling this message
/// </param>
/// <returns>true if message is handled, false otherwise</returns>
private bool HandleDpiChangedMessage(IntPtr wParam, IntPtr lParam)
{
bool handled = false;
if (IsPerMonitorDpiScalingEnabled)
{
var hwndSource = HwndSource.FromHwnd(_hWnd);
if (hwndSource != null)
{
var oldDpi = CurrentDpiScale;
var newDpi =
DpiScale2.FromPixelsPerInch(
NativeMethods.SignedLOWORD(wParam),
NativeMethods.SignedHIWORD(wParam));
if (oldDpi != newDpi)
{
var nativeRect =
Marshal.PtrToStructure<NativeMethods.RECT>(lParam);
var suggestedRect =
new Rect(nativeRect.left, nativeRect.top, nativeRect.Width, nativeRect.Height);
hwndSource.ChangeDpi(
new HwndDpiChangedEventArgs(oldDpi, newDpi, suggestedRect));
handled = true;
}
}
}
return handled;
}
/// <summary>
/// Handler for WM_DPICHANGED_AFTERPARENT
/// </summary>
/// <returns>True if the message is handled, False otherwise</returns>
private bool HandleDpiChangedAfterParentMessage()
{
bool handled = false;
if (IsPerMonitorDpiScalingEnabled)
{
var oldDpi = CurrentDpiScale;
var newDpi = GetDpiScaleForWindow(_hWnd);
if (oldDpi != newDpi)
{
var hwndSource = HwndSource.FromHwnd(_hWnd);
if (hwndSource != null)
{
// During DPI change (and at other times), the parent
// is expected to layout the child. At this point, that layout process
// is expected to have been completed, and the new
// client rect is whatever the *current* client rect
// already happens to be.
var rcClient = SafeNativeMethods.GetClientRect(_hWnd.MakeHandleRef(this));
var clientRect =
new Rect(
rcClient.left,
rcClient.top,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top);
hwndSource.ChangeDpi(new HwndDpiChangedAfterParentEventArgs(oldDpi, newDpi, clientRect));
handled = true;
}
}
}
return handled;
}
/// <summary>
/// The HwndTarget needs to see all windows messages so that
/// it can appropriately react to them.
/// </summary>
internal IntPtr HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam)
{
IntPtr result = Unhandled;
// Handle custom messages with IDs stored in a non-const
// field here.
//
// Handle all other messages with const IDs in the switch-block
// further down.
//
// Note that the guard for 'IsDisposed' is further down, and
// this if/else block is not guarded by that. Be careful of this
// fact when adding additional custom-message handling here. So far,
// the custom-messages being handled here seem immune to the possibility
// of this HwndTarget having been disposed.
if (msg == s_DisplayDevicesAvailabilityChanged)
{
_displayDevicesAvailable = (wparam.ToInt32() != 0);
if (_displayDevicesAvailable && _wasWmPaintProcessingDeferred)
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero, true);
DoPaint();
}
}
else if (msg == s_updateWindowSettings)
{
// Make sure we enable the render target if the window is visible.
if (SafeNativeMethods.IsWindowVisible(_hWnd.MakeHandleRef(this)))
{
UpdateWindowSettings(true);
}
}
else if (msg == s_needsRePresentOnWake)
{
//
// If the session is disconnected (due to machine lock) or in a suspended power
// state, don't invalidate the window immediately, unless
// we're within the allowed failure window after an unlock (See member comments on
// _lastWakeOrUnlockEvent and _allowedPresentFailureDelay for explanation).
// Save the invalidate for when the wake/unlock does occur, so that we avoid the
// WM_PAINT/Invalidate storm, by setting _needsRePresentOnWake.
//
// If we've previously received this message and we don't know that we're
// disconnected or suspended, we may be a window that has been created since a
// lock/disconnect occurred, and thus didn't get the message. Set the
// _nedsRePresentOnWake flag in this case too.
//
TimeSpan delta = DateTime.Now - _lastWakeOrUnlockEvent;
bool fWithinPresentRetryWindow = delta.TotalSeconds < _allowedPresentFailureDelay;
// Either display devices are available, or we are in a 'don't-care' state - ie..,
// running under a non-interactive Window Station.
// Note that running under a non-interactive Window Station is not supported by WPF,
// but we try to keep things working anyway.
bool displayDevicesAvailable = _displayDevicesAvailable || MediaContext.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable;
if (_isSessionDisconnected || _isSuspended ||
(_hasRePresentedSinceWake && !fWithinPresentRetryWindow) ||
!displayDevicesAvailable)
{
_needsRePresentOnWake = true;
}
else
{
if (!_hasRePresentedSinceWake || fWithinPresentRetryWindow)
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero , true);
DoPaint();
_hasRePresentedSinceWake = true;
}
}
return Handled;
}
if (IsDisposed)
{
return result;
}
switch (msg)
{
case WindowMessage.WM_DPICHANGED:
result = HandleDpiChangedMessage(wparam, lparam) ? Handled : Unhandled;
break;
case WindowMessage.WM_DPICHANGED_AFTERPARENT:
result = HandleDpiChangedAfterParentMessage() ? Handled : Unhandled;
break;
case WindowMessage.WM_NCCREATE:
// user32!GetDpiForWindow is only supported on Windows 10 v1607 and later
// IsPerMonitorDpiScalingEnabled tests for this indirectly
if (IsProcessPerMonitorDpiAware == true)
{
UnsafeNativeMethods.EnableNonClientDpiScaling(NormalizeWindow(new HandleRef(this, _hWnd), normalizeChildWindows: false, normalizePopups: true));
}
break;
case WindowMessage.WM_ERASEBKGND:
result = Handled; // Indicates that this message is handled.
break;
case WindowMessage.WM_PAINT:
// If the current Window Station is non-interactive (i.e., NOT WinSta0)
// then we will never find usable display devices. Normally,
// WPF is not supported when running in a non-interactive Window
// Station, for e.g., a typical SCM service calling into WPF UI
// oriented API's is unsupported, and has never been tested. Some
// applications nevertheless do this. When we notice that we are running
// in a non-interactive Window Station, we will try to keep on rendering as best
// as we can, ignoring the fact that actual display devices aren't
// available in this configuration.
if (_displayDevicesAvailable || MediaContext.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable)
{
_wasWmPaintProcessingDeferred = false;
DoPaint();
result = Handled;
}
else
{
_wasWmPaintProcessingDeferred = true;
}
break;
case WindowMessage.WM_SIZE:
//
// When locked on downlevel, MIL stops rendering and invalidates the
// window causing WM_PAINT. When the window is layered and minimized
// before the lock, it'll never get the WM_PAINT on unlock and the MIL will
// never get out of the "don't render" state.
//
// To work around this, we will invalidate ourselves on restore and not
// render while minimized.
//
// If the Window is in minimized state, don't do layout. otherwise, in some cases, it would
// pollute the measure data based on the Minized window size.
if (NativeMethods.IntPtrToInt32(wparam) != NativeMethods.SIZE_MINIMIZED)
{
// Rendering sometimes does not refresh propertly,and results in
// rendering artifacts that look like a patchwork of black unpainted squares.
// This is is caused by a race condition in Windows 7 (and possibly
// Windows Vista, though we haven't observed the effect there).
// Sometimes when we restore from minimized, when we present into the newly
// resized window, the present silently fails, and we end up with garbage in
// our window buffer. This work around queues another invalidate to occur after 100ms.
if (_isMinimized)
{
_restoreDT.Start();
}
_isMinimized = false;
DoPaint();
OnResize();
}
else
{
_isMinimized = true;
}
break;
case WindowMessage.WM_SETTINGCHANGE:
if (OnSettingChange(NativeMethods.IntPtrToInt32(wparam)))
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero , true);
}
break;
case WindowMessage.WM_GETOBJECT:
result = CriticalHandleWMGetobject( wparam, lparam, RootVisual, _hWnd );
break;
case WindowMessage.WM_WINDOWPOSCHANGING:
OnWindowPosChanging(lparam);
break;
case WindowMessage.WM_WINDOWPOSCHANGED:
OnWindowPosChanged(lparam);
break;
case WindowMessage.WM_SHOWWINDOW:
bool enableRenderTarget = (wparam != IntPtr.Zero);
OnShowWindow(enableRenderTarget);
//
//
// When locked on downlevel, MIL stops rendering and invalidates the
// window causing WM_PAINT. When the window is layered and hidden
// before the lock, it won't get the WM_PAINT on unlock and the MIL will
// never get out of the "don't render" state if the window is shown again.
//
// To work around this, we will invalidate the window ourselves on Show().
if (enableRenderTarget)
{
DoPaint();
}
break;
case WindowMessage.WM_ENTERSIZEMOVE:
OnEnterSizeMove();
break;
case WindowMessage.WM_EXITSIZEMOVE:
OnExitSizeMove();
break;
case WindowMessage.WM_STYLECHANGING:
unsafe
{
NativeMethods.STYLESTRUCT * styleStruct = (NativeMethods.STYLESTRUCT *) lparam;
if ((int)wparam == NativeMethods.GWL_EXSTYLE)
{
if(UsesPerPixelOpacity)
{
// We need layered composition to accomplish per-pixel opacity.
//
styleStruct->styleNew |= NativeMethods.WS_EX_LAYERED;
}
else
{
// No properties that require layered composition exist.
// Make sure the layered bit is off.
//
// Note: this prevents an external program from making
// us system-layered (if we are a top-level window).
//
// If we are a child window, we still can't stop our
// parent from being made system-layered, and we will
// end up leaving visual artifacts on the screen under
// WindowsXP.
//
styleStruct->styleNew &= (~NativeMethods.WS_EX_LAYERED);
}
}
}
break;
case WindowMessage.WM_STYLECHANGED:
unsafe
{
bool updateWindowSettings = false;
NativeMethods.STYLESTRUCT * styleStruct = (NativeMethods.STYLESTRUCT *) lparam;
if ((int)wparam == NativeMethods.GWL_STYLE)
{
bool oldIsChild = (styleStruct->styleOld & NativeMethods.WS_CHILD) == NativeMethods.WS_CHILD;
bool newIsChild = (styleStruct->styleNew & NativeMethods.WS_CHILD) == NativeMethods.WS_CHILD;
updateWindowSettings = (oldIsChild != newIsChild);
}
else
{
bool oldIsRTL = (styleStruct->styleOld & NativeMethods.WS_EX_LAYOUTRTL) == NativeMethods.WS_EX_LAYOUTRTL;
bool newIsRTL = (styleStruct->styleNew & NativeMethods.WS_EX_LAYOUTRTL) == NativeMethods.WS_EX_LAYOUTRTL;
updateWindowSettings = (oldIsRTL != newIsRTL);
}
if(updateWindowSettings)
{
UpdateWindowSettings();
}
}
break;
//
// When a Fast User Switch happens, MIL gets an invalid display error when trying to
// render and they invalidate the window resulting in us getting a WM_PAINT. For
// layered windows, we get the WM_PAINT immediately which causes us to
// tell MIL to render and the cycle repeats. On Vista, this creates an infinite loop.
// Downlevel there isn't a loop, but the layered window will never update again.
//
// To work around this problem, we'll make sure not to tell MIL to render when
// we're switched out and will render on coming back.
//
case WindowMessage.WM_WTSSESSION_CHANGE:
// If this message did not originate in our workstation session, then ignore it.
if (_sessionId.HasValue && (_sessionId.Value != lparam.ToInt32()))
{
break;
}
switch (NativeMethods.IntPtrToInt32(wparam))
{
// Session is disconnected. Due to:
// 1. Switched to a different user
// 2. TS logoff
// 3. Screen locked
case NativeMethods.WTS_CONSOLE_DISCONNECT:
case NativeMethods.WTS_REMOTE_DISCONNECT:
case NativeMethods.WTS_SESSION_LOCK:
_hasRePresentedSinceWake = false;
_isSessionDisconnected = true;
_lastWakeOrUnlockEvent = DateTime.MinValue;
break;
// Session is reconnected. See above
case NativeMethods.WTS_CONSOLE_CONNECT:
case NativeMethods.WTS_REMOTE_CONNECT:
case NativeMethods.WTS_SESSION_UNLOCK:
_isSessionDisconnected = false;
if (_needsRePresentOnWake || _wasWmPaintProcessingDeferred)
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero , true);
_needsRePresentOnWake = false;
}
DoPaint();
_lastWakeOrUnlockEvent = DateTime.Now;
break;
default:
break;
}
break;
//
// Downlevel, if we try to present a layered window while suspended the app will crash.
// This has been fixed in Vista but we still need to work around it for older versions
// by not invalidating while suspended.
//
case WindowMessage.WM_POWERBROADCAST:
switch (NativeMethods.IntPtrToInt32(wparam))
{
case NativeMethods.PBT_APMSUSPEND:
_isSuspended = true;
_hasRePresentedSinceWake = false;
_lastWakeOrUnlockEvent = DateTime.MinValue;
break;
case NativeMethods.PBT_APMRESUMESUSPEND:
case NativeMethods.PBT_APMRESUMECRITICAL:
case NativeMethods.PBT_APMRESUMEAUTOMATIC:
_isSuspended = false;
if (_needsRePresentOnWake)
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero , true);
_needsRePresentOnWake = false;
}
DoPaint();
_lastWakeOrUnlockEvent = DateTime.Now;
break;
default:
break;
}
break;
default:
break;
}
return result;
}
private void OnMonitorPowerEvent(object sender, MonitorPowerEventArgs eventArgs)
{
OnMonitorPowerEvent(sender, eventArgs.PowerOn, /*paintOnWake*/true);
}
private void OnMonitorPowerEvent(object sender, bool powerOn, bool paintOnWake)
{
if (powerOn)
{
_isSuspended = false;
if (paintOnWake)
{
if (_needsRePresentOnWake)
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero, true);
_needsRePresentOnWake = false;
}
DoPaint();
}
_lastWakeOrUnlockEvent = DateTime.Now;
}
else
{
_isSuspended = true;
_hasRePresentedSinceWake = false;
_lastWakeOrUnlockEvent = DateTime.MinValue;
}
}
/// <summary>
/// Invalidates self, designed to be called as a DispatcherTimer event handler.
/// </summary>
private void InvalidateSelf(object s, EventArgs args)
{
UnsafeNativeMethods.InvalidateRect(_hWnd.MakeHandleRef(this), IntPtr.Zero, true);
DispatcherTimer sourceDT = (DispatcherTimer)s;
if (sourceDT != null)
{
Debug.Assert(_restoreDT == sourceDT);
sourceDT.Stop();
}
}
/// <summary>
/// Paints a rect
///
/// Note: This gets called a lot to help with layered window problems even when
/// the window isn't layered, but that's okay because rcPaint will be empty.
///
/// </summary>
private void DoPaint()
{
NativeMethods.PAINTSTRUCT ps = new NativeMethods.PAINTSTRUCT();
NativeMethods.HDC hdc;
HandleRef handleRef = new HandleRef(this, _hWnd);
hdc.h = UnsafeNativeMethods.BeginPaint(handleRef, ref ps);
int retval = UnsafeNativeMethods.GetWindowLong(handleRef, NativeMethods.GWL_EXSTYLE);
NativeMethods.RECT rcPaint = new NativeMethods.RECT(ps.rcPaint_left, ps.rcPaint_top, ps.rcPaint_right, ps.rcPaint_bottom);
//
// If we get a BeginPaint with an empty rect then check
// if this is a special layered, non-redirected window
// which would mean we need to do a full paint when it
// won't cause a problem.
//
if (rcPaint.IsEmpty
&& ((retval & NativeMethods.WS_EX_LAYERED) != 0)
&& !UnsafeNativeMethods.GetLayeredWindowAttributes(_hWnd.MakeHandleRef(this), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
&& !_isSessionDisconnected
&& !_isMinimized
&& (!_isSuspended || (UnsafeNativeMethods.GetSystemMetrics(SM.REMOTESESSION) != 0))) // Checking if we are in a remote session works around the fact that power
// notifications for the server monitor are being broad-casted when the
// machine is in a non-local TS session.
{
rcPaint = new NativeMethods.RECT(
0,
0,
_hwndClientRectInScreenCoords.right - _hwndClientRectInScreenCoords.left,
_hwndClientRectInScreenCoords.bottom - _hwndClientRectInScreenCoords.top);
}
AdjustForRightToLeft(ref rcPaint, handleRef);
if (!rcPaint.IsEmpty)
{
InvalidateRect(rcPaint);
}
UnsafeNativeMethods.EndPaint(_hWnd.MakeHandleRef(this), ref ps);
}
internal AutomationPeer EnsureAutomationPeer(Visual root)
{
return EnsureAutomationPeer(root, _hWnd);
}
internal static AutomationPeer EnsureAutomationPeer(Visual root, IntPtr handle)
{
AutomationPeer peer = null;
if (root.CheckFlagsAnd(VisualFlags.IsUIElement))
{
UIElement uiroot = (UIElement)root;
peer = UIElementAutomationPeer.CreatePeerForElement(uiroot);
//there is no specific peer for this UIElement, create a generic root
if(peer == null)
peer = uiroot.CreateGenericRootAutomationPeer();
if(peer != null)
peer.Hwnd = handle;
}
// This can happen if the root visual is not UIElement. In this case,
// attempt to find one in the visual tree.
if (peer == null)
{
peer = UIElementAutomationPeer.GetRootAutomationPeer(root, handle);
}
if (peer != null)
{
peer.AddToAutomationEventList();
}
return peer;
}
private static IntPtr CriticalHandleWMGetobject(IntPtr wparam, IntPtr lparam, Visual root, IntPtr handle)
{
try
{
if (root == null)
{
// Valid case, but need to handle separately. For now, return 0 to avoid exceptions
// in referencing this later on. Real solution is more complex, see WindowsClient#873800.
return IntPtr.Zero;
}
AutomationPeer peer = EnsureAutomationPeer(root, handle);
if (peer == null)
{
return IntPtr.Zero;
}
// get the element proxy
// it's ok to pass the same peer as reference connected peer here because
// it's guaranteed to be a connected one (it's initialized as root already)
IRawElementProviderSimple el = ElementProxy.StaticWrap(peer, peer);
return AutomationInteropProvider.ReturnRawElementProvider(handle, wparam, lparam, el);
}
#pragma warning disable 56500
catch (Exception e)
{
if(CriticalExceptions.IsCriticalException(e))
{
throw;
}
return new IntPtr(Marshal.GetHRForException(e));
}
#pragma warning restore 56500
}
/// <summary>
/// Adjusts a RECT to compensate for Win32 RTL conversion logic
/// </summary>
/// <remarks>
/// When a window is marked with the WS_EX_LAYOUTRTL style, Win32
/// mirrors the coordinates during the various translation APIs.
///
/// Avalon also sets up mirroring transforms so that we properly
/// mirror the output since we render to DirectX, not a GDI DC.
///
/// Unfortunately, this means that our coordinates are already mirrored
/// by Win32, and Avalon mirrors them again. To solve this
/// problem, we un-mirror the coordinates from Win32 before painting
/// in Avalon.
/// </remarks>
/// <param name="rc">
/// The RECT to be adjusted
/// </param>
/// <param name="handleRef">
/// </param>
internal void AdjustForRightToLeft(ref NativeMethods.RECT rc, HandleRef handleRef)
{
int windowStyle = SafeNativeMethods.GetWindowStyle(handleRef, true);
if(( windowStyle & NativeMethods.WS_EX_LAYOUTRTL ) == NativeMethods.WS_EX_LAYOUTRTL)
{
NativeMethods.RECT rcClient = new NativeMethods.RECT();
SafeNativeMethods.GetClientRect(handleRef, ref rcClient);
int width = rc.right - rc.left; // preserve width
rc.right = rcClient.right - rc.left; // set right of rect to be as far from right of window as left of rect was from left of window
rc.left = rc.right - width; // restore width by adjusting left and preserving right
}
}
/// <summary>
/// Force total re-rendering to handle system parameters change
/// (font smoothing settings, gamma correction, etc.)
///</summary>
///<returns>true if rerendering was forced</returns>
private bool OnSettingChange(Int32 firstParam)
{
if ( (int)firstParam == (int)NativeMethods.SPI_SETFONTSMOOTHING ||
(int)firstParam == (int)NativeMethods.SPI_SETFONTSMOOTHINGTYPE ||
(int)firstParam == (int)NativeMethods.SPI_SETFONTSMOOTHINGCONTRAST ||
(int)firstParam == (int)NativeMethods.SPI_SETFONTSMOOTHINGORIENTATION ||
(int)firstParam == (int)NativeMethods.SPI_SETDISPLAYPIXELSTRUCTURE ||
(int)firstParam == (int)NativeMethods.SPI_SETDISPLAYGAMMA ||
(int)firstParam == (int)NativeMethods.SPI_SETDISPLAYCLEARTYPELEVEL ||
(int)firstParam == (int)NativeMethods.SPI_SETDISPLAYTEXTCONTRASTLEVEL
)
{
HRESULT.Check(MILUpdateSystemParametersInfo.Update());
return true;
}
return false;
}
/// <summary>
/// This function should be called to paint the specified
/// region of the window along with any other pending
/// changes. While this function is generally called
/// in response to a WM_PAINT it is up to the user to
/// call BeginPaint and EndPaint or to otherwise validate
/// the bitmap region.
/// </summary>
/// <param name="rcDirty">The rectangle that is dirty.</param>
private void InvalidateRect(NativeMethods.RECT rcDirty)
{
DUCE.ChannelSet channelSet = MediaContext.From(Dispatcher).GetChannels();
DUCE.Channel channel = channelSet.Channel;
DUCE.Channel outOfBandChannel = channelSet.OutOfBandChannel;
// handle InvalidateRect requests only if we have uce resources.
if (_compositionTarget.IsOnChannel(channel))
{
//
// Send a message with the invalid region to the compositor. We create a little batch to send this
// out of order.
//
DUCE.CompositionTarget.Invalidate(
_compositionTarget.GetHandle(outOfBandChannel),
ref rcDirty,
outOfBandChannel);
}
}
/// <summary>
/// Calling this function causes us to update state to reflect a
/// size change of the underlying HWND
/// </summary>
private void OnResize()
{
#if DEBUG
MediaTrace.HwndTarget.Trace("OnResize");
#endif
// handle OnResize requests only if we have uce resources.
if (_compositionTarget.IsOnAnyChannel)
{
MediaContext mctx = MediaContext.From(Dispatcher);
//
// Let the render target know that window size has changed.
//
UpdateWindowSettings();
//
// Push client size chnage to the visual target.
//
Rect clientRect = new Rect(
0,
0,
(float)(Math.Ceiling((double)(_hwndClientRectInScreenCoords.right - _hwndClientRectInScreenCoords.left))),
(float)(Math.Ceiling((double)(_hwndClientRectInScreenCoords.bottom - _hwndClientRectInScreenCoords.top))));
StateChangedCallback(
new object[] { HostStateFlags.ClipBounds, null, clientRect });
mctx.Resize(this);
Int32 style = UnsafeNativeMethods.GetWindowLong(_hWnd.MakeHandleRef(this), NativeMethods.GWL_STYLE);
if (_userInputResize || _usesPerPixelOpacity ||
((style & NativeMethods.WS_CHILD) != 0 && Utilities.IsCompositionEnabled))
{
//
// To ensure that the client area and the non-client area resize
// together, we need to wait, on resize, for the composition
// engine to present the resized frame. The call to CompleteRender
// blocks until that happens.
//
// When the user isn't resizing, the disconnect between client
// and non-client isn't as noticeable so we will err on the side
// of performance for multi-hwnd apps like Visual Studio.
//
// We think syncing is always necessary for layered windows.
//
// For child windows we also need to sync to work-around some DWM issues (see , #782372).
//
mctx.CompleteRender();
}
}
}
/// <summary>
/// Calculates the client and window rectangle in screen coordinates.
/// Calculates the client rectangle relative to its parent
/// </summary>
private void UpdateWindowAndClientCoordinates()
{
HandleRef hWnd = _hWnd.MakeHandleRef(this);
// Update the window rect
SafeNativeMethods.GetWindowRect(hWnd, ref _hwndWindowRectInScreenCoords);
// Get the client rect
NativeMethods.RECT rcClient = new NativeMethods.RECT();
SafeNativeMethods.GetClientRect(hWnd, ref rcClient);
// Convert the client rect to screen coordinates, adjusting for RTL
NativeMethods.POINT ptClientTopLeft = new NativeMethods.POINT(rcClient.left, rcClient.top);
UnsafeNativeMethods.ClientToScreen(hWnd, ref ptClientTopLeft);
NativeMethods.POINT ptClientBottomRight = new NativeMethods.POINT(rcClient.right, rcClient.bottom);
UnsafeNativeMethods.ClientToScreen(hWnd, ref ptClientBottomRight);
if(ptClientBottomRight.x >= ptClientTopLeft.x)
{
_hwndClientRectInScreenCoords.left = ptClientTopLeft.x;
_hwndClientRectInScreenCoords.right = ptClientBottomRight.x;
}
else
{
// RTL windows will cause the right edge to be on the left...
_hwndClientRectInScreenCoords.left = ptClientBottomRight.x;
_hwndClientRectInScreenCoords.right = ptClientTopLeft.x;
}
if(ptClientBottomRight.y >= ptClientTopLeft.y)
{
_hwndClientRectInScreenCoords.top = ptClientTopLeft.y;
_hwndClientRectInScreenCoords.bottom = ptClientBottomRight.y;
}
else
{
// RTL windows will cause the right edge to be on the left...
// This doesn't affect top/bottom, but the code should be symmetrical.
_hwndClientRectInScreenCoords.top = ptClientBottomRight.y;
_hwndClientRectInScreenCoords.bottom = ptClientTopLeft.y;
}
// Need to assert that _hwndClientRectInScreenCoords == _hwndWindowRectInScreenCoords
// when UsesPerPixelOpacity is true
}
/// <summary>
/// Updates <see cref="_worldTransform"/> based on the supplied DPI scale factor
/// </summary>
/// <remarks>Called as part of DPI update processing</remarks>
private void UpdateWorldTransform(DpiScale2 dpiScale)
{
// Occasionally, the world transform can be more than
// a simple DpiScale based transform. This can happen if an
// HWND is :
// (a) a child or a popup
// (b) The hosting behavior of the window is DPI_HOSTING_BEHAVIOR_MIXED
// If these are true, then we will walk the HWND's all the way up and
// multiply the DPI scale factors to create the world transform.
_worldTransform = new MatrixTransform(new Matrix(
dpiScale.DpiScaleX, 0,
0, dpiScale.DpiScaleY,
0, 0));
// Push the transform to render thread.
DUCE.ChannelSet channelSet = MediaContext.From(Dispatcher).GetChannels();
DUCE.Channel channel = channelSet.Channel;
DUCE.ResourceHandle hWorldTransform = ((DUCE.IResource)_worldTransform).AddRefOnChannel(channel);
DUCE.CompositionNode.SetTransform(
_contentRoot.GetHandle(channel),
hWorldTransform,
channel);
}
/// <summary>
/// Updates DPI flags and propagates this all the way to the root-visual
/// </summary>
/// <remarks>Called as part of DPI update processing</remarks>
private void PropagateDpiChangeToRootVisual(DpiScale2 oldDpi, DpiScale2 newDpi)
{
// Update the static array that stores the actual DpiScales.
// output is the index that will be set to the visual flags.
var dpiFlags = DpiUtil.UpdateDpiScalesAndGetIndex(newDpi.PixelsPerInchX, newDpi.PixelsPerInchY);
if (RootVisual != null)
{
// Propagate the visual flags from the RootVisual.
RecursiveUpdateDpiFlagAndInvalidateMeasure(RootVisual, new DpiRecursiveChangeArgs(dpiFlags, oldDpi, newDpi));
}
}
/// <summary>
/// Notifies all listeners that <see cref="_worldTransform"/> and
/// the client rect have changed
/// </summary>
/// <remarks>Called as part of DPI update processing</remarks>
private void NotifyListenersOfWorldTransformAndClipBoundsChanged()
{
var clipBounds = new Rect(
0,
0,
_hwndClientRectInScreenCoords.right - _hwndClientRectInScreenCoords.left,
_hwndClientRectInScreenCoords.bottom - _hwndClientRectInScreenCoords.top);
StateChangedCallback(
new object[]
{
HostStateFlags.WorldTransform |
HostStateFlags.ClipBounds,
_worldTransform.Matrix,
clipBounds
});
}
/// <summary>
/// Resizes and repositions the HWND based on suggestedRect and the new DPI.
/// </summary>
internal void OnDpiChanged(HwndDpiChangedEventArgs e)
{
var oldDpi = CurrentDpiScale;
var newDpi = new DpiScale2(e.NewDpi);
CurrentDpiScale = newDpi;
UpdateWorldTransform(newDpi);
PropagateDpiChangeToRootVisual(oldDpi, newDpi);
NotifyListenersOfWorldTransformAndClipBoundsChanged();
NotifyRendererOfDpiChange(afterParent:false);
// Set the new window size.
UnsafeNativeMethods.SetWindowPos(
_hWnd.MakeHandleRef(this),
new HandleRef(null, IntPtr.Zero), // HWND_TOP
(int)e.SuggestedRect.Left, (int)e.SuggestedRect.Top, (int)e.SuggestedRect.Width, (int)e.SuggestedRect.Height,
NativeMethods.SWP_NOZORDER | NativeMethods.SWP_ASYNCWINDOWPOS);
}
/// <summary>
/// Redraws the child-HWND in the new DPI
/// </summary>
internal void OnDpiChangedAfterParent(HwndDpiChangedAfterParentEventArgs e)
{
var oldDpi = CurrentDpiScale;
var newDpi = new DpiScale2(e.NewDpi);
CurrentDpiScale = newDpi;
UpdateWorldTransform(newDpi);
PropagateDpiChangeToRootVisual(oldDpi, newDpi);
NotifyListenersOfWorldTransformAndClipBoundsChanged();
NotifyRendererOfDpiChange(afterParent:true);
// Update the window position
UnsafeNativeMethods.SetWindowPos(
_hWnd.MakeHandleRef(this),
new HandleRef(null, IntPtr.Zero), // HWND_TOP
(int)e.SuggestedRect.Left, (int)e.SuggestedRect.Top, (int)e.SuggestedRect.Width, (int)e.SuggestedRect.Height,
NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOACTIVATE);
// Invalidates and repaints the client area
UnsafeNativeMethods.InvalidateRect(new HandleRef(this, _hWnd), IntPtr.Zero, true);
DoPaint();
}
private void NotifyRendererOfDpiChange(bool afterParent)
{
DUCE.ChannelSet channelSet = MediaContext.From(Dispatcher).GetChannels();
DUCE.Channel channel = channelSet.Channel;
DUCE.CompositionTarget.ProcessDpiChanged(
_compositionTarget.GetHandle(channel),
CurrentDpiScale,
afterParent,
channel);
}
private void RecursiveUpdateDpiFlagAndInvalidateMeasure(DependencyObject d, DpiRecursiveChangeArgs args)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(d);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(d, i);
if (child != null)
{
RecursiveUpdateDpiFlagAndInvalidateMeasure(child, args);
}
}
Visual visual = d as Visual;
if (visual != null)
{
visual.SetDpiScaleVisualFlags(args);
UIElement element = d as UIElement;
element?.InvalidateMeasure();
}
}
private void OnWindowPosChanging(IntPtr lParam)
{
_windowPosChanging = true;
UpdateWindowPos(lParam);
}
private void OnWindowPosChanged(IntPtr lParam)
{
_windowPosChanging = false;
UpdateWindowPos(lParam);
}
private void UpdateWindowPos(IntPtr lParam)
{
//
// We need to update the window settings used by the render thread when
// 1) The size or position of the render target needs to change
// 2) The render target needs to be enabled or disabled.
//
// Further, we need to synchronize the render thread during sizing operations.
// This is because some APIs that the render thread uses (such as
// UpdateLayeredWindow) have the unintended side-effect of also changing the
// window size. We can't let the render thread and the UI thread fight
// over setting the window size.
//
// Generally, Windows sends our window to messages that bracket the size
// operation:
// 1) WM_WINDOWPOSCHANGING
// Here we synchronize with the render thread, and ask the render thread
// to not render to this window for a while.
// 2) WM_WINDOWPOSCHANGED
// This is after the window size has actually been changed, so we tell
// the render thread that it can render to the window again.
//
// However, there are complications. Sometimes Windows will send a
// WM_WINDOWPOSCHANGING without sending a WM_WINDOWPOSCHANGED. This happens
// when the window size is not really going to change. Also note that
// more than just size/position information is provided by these messages.
// We'll get these messages when nothing but the z-order changes for instance.
//
//
// The first order of business is to determine if the render target
// size or position changed. If so, we need to pass this information to
// the render thread.
//
NativeMethods.WINDOWPOS windowPos = Marshal.PtrToStructure<NativeMethods.WINDOWPOS>(lParam);
bool isMove = (windowPos.flags & NativeMethods.SWP_NOMOVE) == 0;
bool isSize = (windowPos.flags & NativeMethods.SWP_NOSIZE) == 0;
bool positionChanged = (isMove || isSize);
if (positionChanged)
{
//
// We have found that sometimes we get told that the size or position
// of the window has changed, when it really hasn't. So we double
// check here. This is critical because we won't be given a
// WM_WINDOWPOSCHANGED unless the size or position really had changed.
//
if (!isMove)
{
// This is just to avoid any possible integer overflow problems.
windowPos.x = windowPos.y = 0;
}
if (!isSize)
{
// This is just to avoid any possible integer overflow problems.
windowPos.cx = windowPos.cy = 0;
}
//
// WINDOWPOS stores the window coordinates relative to its parent.
// If the parent is NULL, then these are already screen coordinates.
// Otherwise, we need to convert to screen coordinates.
//
NativeMethods.RECT windowRectInScreenCoords = new NativeMethods.RECT(windowPos.x, windowPos.y, windowPos.x + windowPos.cx, windowPos.y + windowPos.cy);
IntPtr hwndParent = UnsafeNativeMethods.GetParent(new HandleRef(null, windowPos.hwnd));
if(hwndParent != IntPtr.Zero)
{
SafeSecurityHelper.TransformLocalRectToScreen(new HandleRef(null, hwndParent), ref windowRectInScreenCoords);
}
if (!isMove)
{
// We weren't actually moving, so the WINDOWPOS structure
// did not contain valid (x,y) information. Just use our
// old values.
int width = (windowRectInScreenCoords.right - windowRectInScreenCoords.left);
int height = (windowRectInScreenCoords.bottom - windowRectInScreenCoords.top);
windowRectInScreenCoords.left = _hwndWindowRectInScreenCoords.left;
windowRectInScreenCoords.right = windowRectInScreenCoords.left + width;
windowRectInScreenCoords.top = _hwndWindowRectInScreenCoords.top;
windowRectInScreenCoords.bottom = windowRectInScreenCoords.top + height;
}
if (!isSize)
{
// We weren't actually sizing, so the WINDOWPOS structure
// did not contain valid (cx,cy) information. Just use our
// old values.
int width = (_hwndWindowRectInScreenCoords.right - _hwndWindowRectInScreenCoords.left);
int height = (_hwndWindowRectInScreenCoords.bottom - _hwndWindowRectInScreenCoords.top);
windowRectInScreenCoords.right = windowRectInScreenCoords.left + width;
windowRectInScreenCoords.bottom = windowRectInScreenCoords.top + height;
}
positionChanged = ( _hwndWindowRectInScreenCoords.left != windowRectInScreenCoords.left
|| _hwndWindowRectInScreenCoords.top != windowRectInScreenCoords.top
|| _hwndWindowRectInScreenCoords.right != windowRectInScreenCoords.right
|| _hwndWindowRectInScreenCoords.bottom != windowRectInScreenCoords.bottom);
}
//
// The second order of business is to determine whether or not the render
// target should be enabled. If we are disabling the render target, then
// we need to synchronize with the render thread. Basically,
// a WM_WINDOWPOSCHANGED always enables the render target it the window is
// visible. And a WM_WINDOWPOSCHANGING will disable the render target
// unless it is not really a size/move, in which case we will not be sent
// a WM_WINDOWPOSCHANGED, so we can't disable the render target.
//
bool enableRenderTarget = SafeNativeMethods.IsWindowVisible(_hWnd.MakeHandleRef(this));
if(enableRenderTarget)
{
if(_windowPosChanging && (positionChanged))
{
enableRenderTarget = false;
}
}
if (positionChanged || (enableRenderTarget != _isRenderTargetEnabled))
{
UpdateWindowSettings(enableRenderTarget);
}
}
bool _windowPosChanging;
private void OnShowWindow(bool enableRenderTarget)
{
if (enableRenderTarget != _isRenderTargetEnabled)
{
UpdateWindowSettings(enableRenderTarget);
}
}
#region PROCESS_DPI_AWARENESS
/// <summary>
/// DPI awareness level of the process. Corresponds to Win32 PROCESS_DPI_AWARENESS
/// enum. Also see <see cref="PROCESS_DPI_AWARENESS"/>
/// </summary>
/// <remarks>
/// i. This value is initialized only once per process, therefore this is a
/// static member.
/// ii. This is an 'effective' value, and not necessarily the 'actual'
/// value. On OS versions older than RS1(Window 10 v1607), WPF does not support
/// per-monitor DPI, and will always default to system aware or unaware modes.
/// To get the actual value, refer to <see cref="AppManifestProcessDpiAwareness"/>
/// </remarks>
private static PROCESS_DPI_AWARENESS? ProcessDpiAwareness { get; set; } = null;
/// <summary>
/// The actual PROCESS_DPI_AWARENESS of the process set by the application manifest,
/// or by an equivalent API, irrespective of the OS version.
/// Also see remarks on <see cref="ProcessDpiAwareness"/>
/// </summary>
/// <remarks>
/// i. This value is initialized only once per process, therefore this is a
/// static member.
/// ii. The initialization of this member depends on <see cref="_hWnd"/>, which is an
/// instance member, so this can't be initialized in the static constructor.
/// We maintain this as a nullable-property to keep track of whether it has been
/// initialized or not. This helps us ensure that <see cref="AppManifestProcessDpiAwareness"/>
/// and <see cref="ProcessDpiAwareness"/> are only initialized once.
/// </remarks>
private static PROCESS_DPI_AWARENESS? AppManifestProcessDpiAwareness { get; set; } = null;
#endregion
/// <summary>
/// Window's DPI Awareness Context, equivalent to a
/// Win32 DPI_AWARENESS_CONTEXT handle
/// </summary>
/// <remarks>
/// - Once set, this will not change again
/// - This is always the 'actual' value, unfiltered by whether WPF
/// is currently operating in per-monitor DPI or better mode.
/// </remarks>
private DpiAwarenessContextValue DpiAwarenessContext { get; set; }
internal DpiScale2 CurrentDpiScale { get; private set; }
internal static bool IsPerMonitorDpiScalingSupportedOnCurrentPlatform
{
get
{
return OSVersionHelper.IsOsWindows10RS1OrGreater;
}
}
internal static bool IsPerMonitorDpiScalingEnabled
{
get
{
return
!CoreAppContextSwitches.DoNotScaleForDpiChanges &&
IsPerMonitorDpiScalingSupportedOnCurrentPlatform;
}
}
internal static bool? IsProcessPerMonitorDpiAware
{
get
{
if (ProcessDpiAwareness.HasValue)
{
return ProcessDpiAwareness.Value == PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE;
}
return null;
}
}
internal static bool? IsProcessSystemAware
{
get
{
if (ProcessDpiAwareness.HasValue)
{
return ProcessDpiAwareness.Value == PROCESS_DPI_AWARENESS.PROCESS_SYSTEM_DPI_AWARE;
}
return null;
}
}
internal static bool? IsProcessUnaware
{
get
{
if (ProcessDpiAwareness.HasValue)
{
return ProcessDpiAwareness.Value == PROCESS_DPI_AWARENESS.PROCESS_DPI_UNAWARE;
}
return null;
}
}
internal bool IsWindowPerMonitorDpiAware
{
get
{
return
DpiAwarenessContext == DpiAwarenessContextValue.PerMonitorAware ||
DpiAwarenessContext == DpiAwarenessContextValue.PerMonitorAwareVersion2;
}
}
private void OnEnterSizeMove()
{
_userInputResize = true;
}
private void OnExitSizeMove()
{
if (_windowPosChanging)
{
_windowPosChanging = false;
UpdateWindowSettings(true);
}
_userInputResize = false;
}
private void UpdateWindowSettings()
{
UpdateWindowSettings(_isRenderTargetEnabled, null);
}
private void UpdateWindowSettings(bool enableRenderTarget)
{
UpdateWindowSettings(enableRenderTarget, null);
}
private void UpdateWindowSettings(bool enableRenderTarget, DUCE.ChannelSet? channelSet)
{
MediaContext mctx = MediaContext.From(Dispatcher);
// It's possible that this method could be called multiple times in a row
// with the same enableRenderTarget value and we'd like to minimize the
// number of flushes on the OOB channel by only flushing when transitioning
// rather than ever time we get a disable.
bool firstTimeRenderTargetDisabled = false;
bool firstTimeRenderTargetEnabled = false;
if (_isRenderTargetEnabled != enableRenderTarget)
{
_isRenderTargetEnabled = enableRenderTarget;
firstTimeRenderTargetDisabled = !enableRenderTarget;
firstTimeRenderTargetEnabled = enableRenderTarget;
// Basic idea: the render thread and the UI thread have a
// race condition when the UI thread wants to modify
// HWND data and the render thread is using it. The render
// thread can paint garbage on the screen, and it can also
// cause the old data to be set again (ULW issue, hence ULWEx).
//
// So we tell the render thread to stop rendering and then we
// wait for them to stop when disabling the render target by
// issuing the UpdateWindowSettings command synchronously on
// an out-of-band channel.
}
// if we are disconnected we are done.
if (!_compositionTarget.IsOnAnyChannel)
{
return;
}
//
// Calculate the client rectangle in screen coordinates.
//
UpdateWindowAndClientCoordinates();
Int32 style = UnsafeNativeMethods.GetWindowLong(_hWnd.MakeHandleRef(this), NativeMethods.GWL_STYLE);
Int32 exStyle = UnsafeNativeMethods.GetWindowLong(_hWnd.MakeHandleRef(this), NativeMethods.GWL_EXSTYLE);
bool isLayered = (exStyle & NativeMethods.WS_EX_LAYERED) != 0;
bool isChild = (style & NativeMethods.WS_CHILD) != 0;
bool isRTL = (exStyle & NativeMethods.WS_EX_LAYOUTRTL) != 0;
int width = _hwndClientRectInScreenCoords.right - _hwndClientRectInScreenCoords.left;
int height = _hwndClientRectInScreenCoords.bottom - _hwndClientRectInScreenCoords.top;
MILTransparencyFlags flags = MILTransparencyFlags.Opaque;
// if (!DoubleUtil.AreClose(_opacity, 1.0))
// {
// flags |= MILTransparencyFlags.ConstantAlpha;
// }
// if (_colorKey.HasValue)
// {
// flags |= MILTransparencyFlags.ColorKey;
// }
if (_usesPerPixelOpacity)
{
flags |= MILTransparencyFlags.PerPixelAlpha;
}
if (!isLayered && flags != MILTransparencyFlags.Opaque)
{
// The window is not layered, but it should be -- set the layered flag.
UnsafeNativeMethods.SetWindowLong(_hWnd.MakeHandleRef(this), NativeMethods.GWL_EXSTYLE, new IntPtr(exStyle | NativeMethods.WS_EX_LAYERED));
}
else if (isLayered && flags == MILTransparencyFlags.Opaque)
{
// The window is layered but should not be -- unset the layered flag.
UnsafeNativeMethods.SetWindowLong(_hWnd.MakeHandleRef(this), NativeMethods.GWL_EXSTYLE, new IntPtr(exStyle & ~NativeMethods.WS_EX_LAYERED));
}
else if(isLayered && flags != MILTransparencyFlags.Opaque && _isRenderTargetEnabled && (width == 0 || height == 0))
{
// The window is already layered, and it should be. But we are enabling a window
// that is has a 0-size dimension. This may cause us to leave the last sprite
// on the screen. The best way to get rid of this is to just make the entire
// sprite transparent.
NativeMethods.BLENDFUNCTION blend = new NativeMethods.BLENDFUNCTION();
blend.BlendOp = NativeMethods.AC_SRC_OVER;
blend.SourceConstantAlpha = 0; // transparent
unsafe
{
UnsafeNativeMethods.UpdateLayeredWindow(_hWnd.h, IntPtr.Zero, null, null, IntPtr.Zero, null, 0, ref blend, NativeMethods.ULW_ALPHA);
}
}
isLayered = (flags != MILTransparencyFlags.Opaque);
if (channelSet == null)
{
channelSet = mctx.GetChannels();
}
// If this is the first time going from disabled -> enabled, flush
// the out of band to make sure all disable packets have been
// processed before sending the enable later below. Otherwise,
// the enable could be ignored if the disable cookie doesn't match
DUCE.Channel outOfBandChannel = channelSet.Value.OutOfBandChannel;
if (firstTimeRenderTargetEnabled)
{
outOfBandChannel.Commit();
outOfBandChannel.SyncFlush();
}
// Every UpdateWindowSettings command that disables the render target is
// assigned a new cookie. Every UpdateWindowSettings command that enables
// the render target uses the most recent cookie. This allows the
// compositor to ignore UpdateWindowSettings(enable) commands that come
// out of order due to us disabling out-of-band and enabling in-band.
if (!_isRenderTargetEnabled)
{
_disableCookie++;
}
//
// When enabling the render target, stay in-band. This allows any
// client-side rendering instructions to be included in the same packet.
// Otherwise pass in the OutOfBand handle.
//
DUCE.Channel channel = channelSet.Value.Channel;
DUCE.CompositionTarget.UpdateWindowSettings(
_isRenderTargetEnabled ? _compositionTarget.GetHandle(channel) : _compositionTarget.GetHandle(outOfBandChannel),
_hwndClientRectInScreenCoords,
Colors.Transparent, // _colorKey.GetValueOrDefault(Colors.Black),
1.0f, // (float)_opacity,
isLayered ? (_usesPerPixelOpacity ? MILWindowLayerType.ApplicationManagedLayer : MILWindowLayerType.SystemManagedLayer) : MILWindowLayerType.NotLayered,
flags,
isChild,
isRTL,
_isRenderTargetEnabled,
_disableCookie,
_isRenderTargetEnabled ? channel : outOfBandChannel);
if (_isRenderTargetEnabled)
{
//
// Re-render the visual tree.
//
mctx.PostRender();
}
else
{
if (firstTimeRenderTargetDisabled)
{
outOfBandChannel.CloseBatch();
outOfBandChannel.Commit();
//
// Wait for the command to be processed -- sync flush will take care
// of that while being safe w.r.t. zombie partitions.
//
outOfBandChannel.SyncFlush();
}
// If we disabled the render target, we run the risk of leaving it disabled.
// One such example is when a window is programatically sized, but then
// GetMinMaxInfo denies the change. We do not receive any message that would
// allow us to re-enable the render targer. To cover these odd cases, we
// post ourselves a message to possible re-enable the render target when
// we are done with the current message processing.
UnsafeNativeMethods.PostMessage(new HandleRef(this, _hWnd), s_updateWindowSettings, IntPtr.Zero, IntPtr.Zero);
}
}
/// <summary>
/// Gets and sets the root Visual of this HwndTarget.
/// </summary>
/// <remarks>
/// Callers must have UIPermission(UIPermissionWindow.AllWindows) to call this API.
/// </remarks>
public override Visual RootVisual
{
set
{
base.RootVisual = value;
if (value != null)
{
// Update the static array that stores the actual DpiScales.
// output is the index that will be set to the visual flags.
if (IsProcessPerMonitorDpiAware == true)
{
DpiFlags dpiFlags = DpiUtil.UpdateDpiScalesAndGetIndex(CurrentDpiScale.PixelsPerInchX, CurrentDpiScale.PixelsPerInchY);
DpiScale newDpiScale = new DpiScale(UIElement.DpiScaleXValues[dpiFlags.Index], UIElement.DpiScaleYValues[dpiFlags.Index]);
RootVisual.RecursiveSetDpiScaleVisualFlags(new DpiRecursiveChangeArgs( dpiFlags, RootVisual.GetDpi(), newDpiScale));
}
// UIAutomation listens for the EventObjectUIFragmentCreate WinEvent to
// understand when UI that natively implements UIAutomation comes up
// Need to figure out how to handle when _rootVisual is replaced above (is there some
// event when this happens?); MS.Internal.Automation.NativeEventListener may have a context
// monitor that is holding onto the old _rootVisual and that would need to be cleaned up.
// Do we treat swapping in a new root as a new app? Need to understand when this could happen.
UnsafeNativeMethods.NotifyWinEvent(UnsafeNativeMethods.EventObjectUIFragmentCreate, _hWnd.MakeHandleRef(this), 0, 0);
}
}
}
/// <summary>
/// Returns matrix that can be used to transform coordinates from this
/// target to the rendering destination device.
/// </summary>
public override Matrix TransformToDevice
{
get
{
VerifyAPIReadOnly();
Matrix m = Matrix.Identity;
m.Scale(CurrentDpiScale.DpiScaleX, CurrentDpiScale.DpiScaleY);
return m;
}
}
/// <summary>
/// Returns matrix that can be used to transform coordinates from
/// the rendering destination device to this target.
/// </summary>
public override Matrix TransformFromDevice
{
get
{
VerifyAPIReadOnly();
Matrix m = Matrix.Identity;
m.Scale(1.0f/CurrentDpiScale.DpiScaleX, 1.0f/CurrentDpiScale.DpiScaleY);
return m;
}
}
/// <summary>
/// This is the color that is drawn before everything else. If
/// this color has an alpha component other than 1 it will be ignored.
/// </summary>
public Color BackgroundColor
{
get
{
VerifyAPIReadOnly();
return _backgroundColor;
}
set
{
VerifyAPIReadWrite();
if (_backgroundColor != value)
{
_backgroundColor = value;
MediaContext mctx = MediaContext.From(Dispatcher);
DUCE.ChannelSet channelSet = mctx.GetChannels();
DUCE.Channel channel = channelSet.Channel;
if (channel == null)
{
// MediaContext is in disconnected state, so we will send
// the clear color when CreateUCEResources gets called
Debug.Assert(!_compositionTarget.IsOnChannel(channel));
}
else
{
DUCE.CompositionTarget.SetClearColor(
_compositionTarget.GetHandle(channel),
_backgroundColor,
channel);
mctx.PostRender();
}
}
}
}
// /// <summary>
// /// Specifies the color to display as transparent.
// /// </summary>
// /// <remarks>
// /// Use null to indicate that no color should be transparent.
// /// </remarks>
// public Nullable<Color> ColorKey
// {
// get
// {
// VerifyAPIReadOnly();
//
// return _colorKey;
// }
//
// set
// {
// VerifyAPIReadWrite();
//
// if(_colorKey != value)
// {
// _colorKey = value;
//
// UpdateWindowSettings();
// }
// }
// }
// /// <summary>
// /// Specifies the constant opacity to apply to the window.
// /// </summary>
// /// <remarks>
// /// The valid values range from [0..1]. Values outside of this range are clamped.
// /// </remarks>
// public double Opacity
// {
// get
// {
// VerifyAPIReadOnly();
//
// return _opacity;
// }
//
// set
// {
// VerifyAPIReadWrite();
//
// if(value < 0.0) value = 0.0;
// if(value > 1.0) value = 1.0;
//
// if(!MS.Internal.DoubleUtil.AreClose(value, _opacity))
// {
// _opacity = value;
//
// UpdateWindowSettings();
// }
// }
// }
/// <summary>
/// Specifies whether or not the per-pixel opacity of the window content
/// is respected.
/// </summary>
/// <remarks>
/// By enabling per-pixel opacity, the system will no longer draw the non-client area.
/// </remarks>
public bool UsesPerPixelOpacity
{
get
{
VerifyAPIReadOnly();
return _usesPerPixelOpacity;
}
internal set
{
VerifyAPIReadWrite();
if(_usesPerPixelOpacity != value)
{
_usesPerPixelOpacity = value;
UpdateWindowSettings();
}
}
}
#region Notification Window
[ThreadStatic]
private static NotificationWindowHelper _notificationWindowHelper;
private void EnsureNotificationWindow()
{
if (_notificationWindowHelper == null)
{
_notificationWindowHelper = new NotificationWindowHelper();
}
}
private class MonitorPowerEventArgs : EventArgs
{
public MonitorPowerEventArgs(bool powerOn)
{
PowerOn = powerOn;
}
public bool PowerOn { get; private set; }
}
/// <summary>
/// Abstraction for the logic to get thread level
/// system notifications like PBT_POWERSETTINGCHANGE.
/// Ideally all such thread singleton messages for
/// hwnds of HwndTargets should be recieved by this
/// class. Only PBT_POWERSETTINGCHANGE is implemented
/// at this point, so as to limit the testing surface.
/// Others must be implemented in future as and when
/// possible.
/// </summary>
private class NotificationWindowHelper : IDisposable
{
#region Data
/// <SecurityNode>
/// Critical: We dont want _notificationHwnd to be exposed and used
/// by anyone besides this class.
/// </SecurityNode>
private HwndWrapper _notificationHwnd; // The hwnd used to listen system wide messages
/// <SecurityNode>
/// Critical: _notificationHook is the hook to listen to window
/// messages. We want this to be critical that no one can get it
/// listen to window messages.
/// </SecurityNode>
private HwndWrapperHook _notificationHook;
private int _hwndTargetCount;
public event EventHandler<MonitorPowerEventArgs> MonitorPowerEvent;
private bool _monitorOn = true;
private IntPtr _hPowerNotify;
#endregion
/// <SecurityNode>
/// Critical: Calls critical code.
/// TreatAsSafe: Doesn't expose the critical resource.
/// </SecurityNode>
public NotificationWindowHelper()
{
// Check for Vista or newer is needed for RegisterPowerSettingNotification.
// This check needs to rescoped to the said method call, if other
// notifications are implemented.
if (Utilities.IsOSVistaOrNewer)
{
// _notificationHook needs to be member variable otherwise
// it is GC'ed and we don't get messages from HwndWrapper
// (HwndWrapper keeps a WeakReference to the hook)
_notificationHook = new HwndWrapperHook(NotificationFilterMessage);
HwndWrapperHook[] wrapperHooks = { _notificationHook };
_notificationHwnd = new HwndWrapper(
0,
0,
0,
0,
0,
0,
0,
"",
IntPtr.Zero,
wrapperHooks);
Guid monitorGuid = new Guid(NativeMethods.GUID_MONITOR_POWER_ON.ToByteArray());
unsafe
{
_hPowerNotify = UnsafeNativeMethods.RegisterPowerSettingNotification(_notificationHwnd.Handle, &monitorGuid, 0);
}
}
}
/// <SecurityNode>
/// Critical: Calls critical code.
/// TreatAsSafe: Doesn't expose the critical resource.
/// </SecurityNode>
public void Dispose()
{
if (_hPowerNotify != IntPtr.Zero)
{
UnsafeNativeMethods.UnregisterPowerSettingNotification(_hPowerNotify);
_hPowerNotify = IntPtr.Zero;
}
// Remove any attached event handlers.
MonitorPowerEvent = null;
_hwndTargetCount = 0;
if (_notificationHwnd != null)
{
_notificationHwnd.Dispose();
_notificationHwnd = null;
}
}
/// <SecurityNode>
/// Critical: Calls critical code.
/// TreatAsSafe: Doesn't expose the critical resource.
/// </SecurityNode>
public void AttachHwndTarget(HwndTarget hwndTarget)
{
Debug.Assert(hwndTarget != null);
MonitorPowerEvent += hwndTarget.OnMonitorPowerEvent;
if (_hwndTargetCount > 0)
{
// Every hwnd which registers to listen PBT_POWERSETTINGCHANGE
// gets the message atleast once so as to set the appropriate
// state. This call to the event handler simulates similar
// behavior. It is too early for the hwnd to paint, hence
// pass paintOnWake=false assuming that it will soon get
// a WM_PAINT message.
hwndTarget.OnMonitorPowerEvent(null, _monitorOn, /*paintOnWake*/ false);
}
_hwndTargetCount++;
}
/// <SecurityNode>
/// Critical: Calls critical code.
/// TreatAsSafe: Doesn't expose the critical resource.
/// </SecurityNode>
public bool DetachHwndTarget(HwndTarget hwndTarget)
{
Debug.Assert(hwndTarget != null);
MonitorPowerEvent -= hwndTarget.OnMonitorPowerEvent;
_hwndTargetCount--;
Debug.Assert(_hwndTargetCount >= 0);
return (_hwndTargetCount == 0);
}
/// <summary>
/// Handles the messages for the notification window
/// </summary>
private IntPtr NotificationFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
IntPtr retInt = IntPtr.Zero;
switch ((WindowMessage)msg)
{
case WindowMessage.WM_POWERBROADCAST:
switch (NativeMethods.IntPtrToInt32(wParam))
{
case NativeMethods.PBT_POWERSETTINGCHANGE:
// PBT_POWERSETTINGCHANGE logic is implemented as a thread singleton
// instead of application singleton so as to avoid race between
// notification hwnd's PBT_POWERSETTINGCHANGE and other thread
// hwnd's WM_PAINT.
unsafe
{
NativeMethods.POWERBROADCAST_SETTING* powerBroadcastSetting = (NativeMethods.POWERBROADCAST_SETTING*)lParam;
if ((*powerBroadcastSetting).PowerSetting == NativeMethods.GUID_MONITOR_POWER_ON)
{
if ((*powerBroadcastSetting).Data == 0)
{
// Monitor is off
_monitorOn = false;
}
else
{
// Monitor is on
_monitorOn = true;
}
if (MonitorPowerEvent != null)
{
MonitorPowerEvent(null, new MonitorPowerEventArgs(_monitorOn));
}
}
}
break;
}
break;
default:
handled = false;
break;
}
return retInt;
}
}
#endregion
}
}
|