|
7 | 7 | using System.Windows.Media;
|
8 | 8 | using System.Runtime.InteropServices;
|
9 | 9 | using System.Diagnostics;
|
| 10 | +using System.Reflection; |
10 | 11 | using System.Security;
|
11 | 12 | using MS.Internal;
|
12 | 13 | using MS.Internal.Interop;
|
@@ -908,12 +909,12 @@ private void PossiblyDeactivate(IntPtr hwndCapture, bool stillActiveIfOverSelf)
|
908 | 909 | //Console.WriteLine("PossiblyDeactivate(" + hwndCapture + ")");
|
909 | 910 |
|
910 | 911 | // We are now longer active ourselves, but it is possible that the
|
911 |
| - // window the mouse is going to intereact with is in the same |
| 912 | + // window the mouse is going to interact with is in the same |
912 | 913 | // Dispatcher as ourselves. If so, we don't want to deactivate the
|
913 | 914 | // mouse input stream because the other window hasn't activated it
|
914 | 915 | // yet, and it may result in the input stream "flickering" between
|
915 |
| - // active/inactive/active. This is ugly, so we try to supress the |
916 |
| - // uneccesary transitions. |
| 916 | + // active/inactive/active. This is ugly, so we try to suppress the |
| 917 | + // unnecessary transitions. |
917 | 918 | //
|
918 | 919 | IntPtr hwndToCheck = hwndCapture;
|
919 | 920 | if(hwndToCheck == IntPtr.Zero)
|
@@ -951,17 +952,16 @@ private void PossiblyDeactivate(IntPtr hwndCapture, bool stillActiveIfOverSelf)
|
951 | 952 | {
|
952 | 953 | // We need to check if the point is over the client or
|
953 | 954 | // non-client area. We only care about being over the
|
954 |
| - // non-client area. |
| 955 | + // client area. |
955 | 956 | try
|
956 | 957 | {
|
957 |
| - NativeMethods.RECT rcClient = new NativeMethods.RECT(); |
958 |
| - SafeNativeMethods.GetClientRect(new HandleRef(this,hwndToCheck), ref rcClient); |
| 958 | + NativeMethods.RECT rcClient = GetEffectiveClientRect(hwndToCheck); |
959 | 959 | SafeNativeMethods.ScreenToClient(new HandleRef(this,hwndToCheck), ptCursor);
|
960 | 960 |
|
961 | 961 | if(ptCursor.x < rcClient.left || ptCursor.x >= rcClient.right ||
|
962 | 962 | ptCursor.y < rcClient.top || ptCursor.y >= rcClient.bottom)
|
963 | 963 | {
|
964 |
| - // We are not over the non-client area. We can bail out. |
| 964 | + // We are not over the client area. We can bail out. |
965 | 965 | //Console.WriteLine(" No capture, mouse outside of client area.");
|
966 | 966 | //Console.WriteLine(" Client Area: ({0},{1})-({2},{3})", rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
|
967 | 967 | //Console.WriteLine(" Mouse: ({0},{1})", ptCursor.x, ptCursor.y);
|
@@ -1005,6 +1005,139 @@ private void PossiblyDeactivate(IntPtr hwndCapture, bool stillActiveIfOverSelf)
|
1005 | 1005 | }
|
1006 | 1006 | }
|
1007 | 1007 |
|
| 1008 | + /// <summary> |
| 1009 | + /// Returns the effective client rect of the hwnd, for use by PossiblyDeactivate |
| 1010 | + /// when deciding whether to do the "don't deactivate mouse input stream" |
| 1011 | + /// optimization. Specifically: |
| 1012 | + /// o If the hwnd isn't ours, return Empty. We never want to do the |
| 1013 | + /// optimization in this case. |
| 1014 | + /// o If the hwnd has no custom chrome, return its native client rect. |
| 1015 | + /// o Otherwise, remove the invisible caption and resize-border areas |
| 1016 | + /// from the native client rect, and return the result. |
| 1017 | + /// The last case deactivates the mouse stream when the mouse enters the |
| 1018 | + /// "effective" non-client area of a custom-chromed window. Leaving it |
| 1019 | + /// active leads to mouse events intended only for DWM to be delivered |
| 1020 | + /// to our windows, running user's event handlers at inappropriate times, |
| 1021 | + /// which can cause more or less arbitrary damage. |
| 1022 | + /// |
| 1023 | + /// For example, moving the mouse from one window into the caption area |
| 1024 | + /// of a chromed window, then press-and-drag in the caption area will |
| 1025 | + /// (correctly) cause DWM to move the window, but will also (incorrectly) |
| 1026 | + /// raise MouseMove events to both windows. [DDVSO 1245503] |
| 1027 | + /// </summary> |
| 1028 | + private NativeMethods.RECT GetEffectiveClientRect(IntPtr hwnd) |
| 1029 | + { |
| 1030 | + NativeMethods.RECT rcClient = new NativeMethods.RECT(); |
| 1031 | + HwndSource hwndSource; |
| 1032 | + |
| 1033 | + // if the hwnd isn't ours, return an empty rect |
| 1034 | + if (!IsOurWindowImpl(hwnd, out hwndSource)) |
| 1035 | + { |
| 1036 | + return rcClient; |
| 1037 | + } |
| 1038 | + |
| 1039 | + // if the hwnd has custom chrome, return the reduced client area |
| 1040 | + if (HasCustomChrome(hwndSource, ref rcClient)) |
| 1041 | + { |
| 1042 | + return rcClient; |
| 1043 | + } |
| 1044 | + |
| 1045 | + // otherwise, return the native client rect |
| 1046 | + SafeNativeMethods.GetClientRect(new HandleRef(this,hwnd), ref rcClient); |
| 1047 | + return rcClient; |
| 1048 | + } |
| 1049 | + |
| 1050 | + /// <summary> |
| 1051 | + /// If the given hwndSource has custom chrome via WindowChrome, return |
| 1052 | + /// true and set rcClient to the effective client area. |
| 1053 | + /// </summary> |
| 1054 | + private bool HasCustomChrome(HwndSource hwndSource, ref NativeMethods.RECT rcClient) |
| 1055 | + { |
| 1056 | + if (!EnsureFrameworkAccessors(hwndSource)) |
| 1057 | + { |
| 1058 | + return false; |
| 1059 | + } |
| 1060 | + |
| 1061 | + // an hwnd has custom chrome if its root visual is a Window whose |
| 1062 | + // WindowChromeWorker property is set. We can't say it that way here, |
| 1063 | + // because those classes belong to PresentationFramework. But we |
| 1064 | + // can do the equivalent using dependency properties and reflection. |
| 1065 | + DependencyObject rootVisual = hwndSource.RootVisual; |
| 1066 | + |
| 1067 | + DependencyObject windowChromeWorker = rootVisual?.GetValue(WindowChromeWorkerProperty) as DependencyObject; |
| 1068 | + if (windowChromeWorker == null) |
| 1069 | + { |
| 1070 | + return false; |
| 1071 | + } |
| 1072 | + |
| 1073 | + // ask the worker for the effective client area |
| 1074 | + object[] args = new object[1] { rcClient }; |
| 1075 | + if ((bool)GetEffectiveClientAreaMI.Invoke(windowChromeWorker, args)) |
| 1076 | + { |
| 1077 | + // copy the answer back to our caller |
| 1078 | + rcClient = (NativeMethods.RECT)(args[0]); |
| 1079 | + return true; |
| 1080 | + } |
| 1081 | + |
| 1082 | + return false; |
| 1083 | + } |
| 1084 | + |
| 1085 | + // lazy initialization of static fields that have to be |
| 1086 | + // set by reflection into PresentationFramework |
| 1087 | + private bool EnsureFrameworkAccessors(HwndSource hwndSource) |
| 1088 | + { |
| 1089 | + // if we've already done the work, return |
| 1090 | + if (WindowChromeWorkerProperty != null) |
| 1091 | + { |
| 1092 | + return true; |
| 1093 | + } |
| 1094 | + |
| 1095 | + // get a reference to PresentationFramework, either from our own |
| 1096 | + // HwndSource, or (as a fallback) from the target HwndSource |
| 1097 | + Assembly presentationFramework = GetPresentationFrameworkFromHwndSource(_source.Value); |
| 1098 | + if (presentationFramework == null) |
| 1099 | + { |
| 1100 | + presentationFramework = GetPresentationFrameworkFromHwndSource(hwndSource); |
| 1101 | + } |
| 1102 | + |
| 1103 | + if (presentationFramework == null) |
| 1104 | + { |
| 1105 | + return false; |
| 1106 | + } |
| 1107 | + |
| 1108 | + // reflect into PresentationFramework to the accessors we need |
| 1109 | + Type windowChromeWorker = presentationFramework.GetType("System.Windows.Shell.WindowChromeWorker"); |
| 1110 | + FieldInfo fiWindowChromeWorkerProperty = windowChromeWorker?.GetField("WindowChromeWorkerProperty", BindingFlags.Static | BindingFlags.Public); |
| 1111 | + DependencyProperty windowChromeWorkerProperty = fiWindowChromeWorkerProperty?.GetValue(null) as DependencyProperty; |
| 1112 | + GetEffectiveClientAreaMI = windowChromeWorker?.GetMethod("GetEffectiveClientArea", BindingFlags.Instance | BindingFlags.NonPublic); |
| 1113 | + |
| 1114 | + // if we got them all, set WindowChromeWorkerProperty to signal that the |
| 1115 | + // initialization succeeded. This method can run on multiple threads, |
| 1116 | + // but it sets the static members to the same value on each thread, so |
| 1117 | + // it doesn't need any locking. |
| 1118 | + if (windowChromeWorkerProperty != null && GetEffectiveClientAreaMI != null) |
| 1119 | + { |
| 1120 | + WindowChromeWorkerProperty = windowChromeWorkerProperty; |
| 1121 | + } |
| 1122 | + |
| 1123 | + return (WindowChromeWorkerProperty != null); |
| 1124 | + } |
| 1125 | + |
| 1126 | + // return the Assembly for PresentationFramework. Usually the root visual |
| 1127 | + // of the given HwndSource is an object whose type is (or derives from) |
| 1128 | + // a type in PF: Window, PopupRoot, etc. |
| 1129 | + private Assembly GetPresentationFrameworkFromHwndSource(HwndSource hwndSource) |
| 1130 | + { |
| 1131 | + DependencyObject rootVisual = hwndSource?.RootVisual; |
| 1132 | + |
| 1133 | + Type type = rootVisual?.GetType(); |
| 1134 | + while (type != null && type.Assembly.FullName != PresentationFrameworkAssemblyFullName) |
| 1135 | + { |
| 1136 | + type = type.BaseType; |
| 1137 | + } |
| 1138 | + |
| 1139 | + return type?.Assembly; |
| 1140 | + } |
1008 | 1141 | private void StartTracking(IntPtr hwnd)
|
1009 | 1142 | {
|
1010 | 1143 | if(!_tracking && !_isDwmProcess)
|
@@ -1047,14 +1180,20 @@ private IntPtr MakeLPARAM(int high, int low)
|
1047 | 1180 | }
|
1048 | 1181 |
|
1049 | 1182 | private bool IsOurWindow(IntPtr hwnd)
|
| 1183 | + { |
| 1184 | + HwndSource hwndSource; |
| 1185 | + return IsOurWindowImpl(hwnd, out hwndSource); |
| 1186 | + } |
| 1187 | + |
| 1188 | + private bool IsOurWindowImpl(IntPtr hwnd, out HwndSource hwndSource) |
1050 | 1189 | {
|
1051 | 1190 | bool isOurWindow = false;
|
| 1191 | + hwndSource = null; |
1052 | 1192 |
|
1053 | 1193 | Debug.Assert(null != _source && null != _source.Value);
|
1054 | 1194 |
|
1055 | 1195 | if(hwnd != IntPtr.Zero)
|
1056 | 1196 | {
|
1057 |
| - HwndSource hwndSource; |
1058 | 1197 | hwndSource = HwndSource.CriticalFromHwnd(hwnd);
|
1059 | 1198 |
|
1060 | 1199 | if(hwndSource != null)
|
@@ -1341,6 +1480,11 @@ private void RecordMouseMove(int x, int y, int timestamp)
|
1341 | 1480 |
|
1342 | 1481 | private NativeMethods.TRACKMOUSEEVENT _tme = new NativeMethods.TRACKMOUSEEVENT();
|
1343 | 1482 |
|
| 1483 | + // accessors into PresentationFramework classes |
| 1484 | + const string PresentationFrameworkAssemblyFullName = "PresentationFramework, Version=" + BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + BuildInfo.WCP_PUBLIC_KEY_TOKEN; |
| 1485 | + private static DependencyProperty WindowChromeWorkerProperty; |
| 1486 | + private static MethodInfo GetEffectiveClientAreaMI; |
| 1487 | + |
1344 | 1488 | // MITIGATION_SETCURSOR
|
1345 | 1489 | //
|
1346 | 1490 | // Windows can have a "context help" button in the title bar. When the user
|
|
0 commit comments