1 /* 2 KeePass Password Safe - The Open-Source Password Manager 3 Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20 using System; 21 using System.Collections.Generic; 22 using System.Diagnostics; 23 using System.Drawing; 24 using System.Text; 25 using System.Windows.Forms; 26 27 using KeePass.App; 28 using KeePass.Forms; 29 using KeePass.Native; 30 using KeePass.Util; 31 32 using KeePassLib.Utility; 33 34 namespace KeePass.UI 35 { 36 public sealed class GwmWindowEventArgs : EventArgs 37 { 38 private Form m_form; 39 public Form Form 40 { 41 get { return m_form; } 42 } 43 44 private IGwmWindow m_gwmWindow; 45 public IGwmWindow GwmWindow 46 { 47 get { return m_gwmWindow; } 48 } 49 GwmWindowEventArgs(Form form, IGwmWindow gwmWindow)50 public GwmWindowEventArgs(Form form, IGwmWindow gwmWindow) 51 { 52 m_form = form; 53 m_gwmWindow = gwmWindow; 54 } 55 } 56 57 public static class GlobalWindowManager 58 { 59 private static List<KeyValuePair<Form, IGwmWindow>> g_vWindows = 60 new List<KeyValuePair<Form, IGwmWindow>>(); 61 private static List<CommonDialog> g_vDialogs = new List<CommonDialog>(); 62 63 private static readonly object g_oSyncRoot = new object(); 64 65 public static uint WindowCount 66 { 67 get 68 { 69 uint u; 70 lock(g_oSyncRoot) 71 { 72 u = ((uint)(g_vWindows.Count + g_vDialogs.Count) + 73 MessageService.CurrentMessageCount); 74 } 75 return u; 76 } 77 } 78 79 public static bool CanCloseAllWindows 80 { 81 get 82 { 83 lock(g_oSyncRoot) 84 { 85 if(g_vDialogs.Count > 0) return false; 86 if(MessageService.CurrentMessageCount > 0) return false; 87 88 foreach(KeyValuePair<Form, IGwmWindow> kvp in g_vWindows) 89 { 90 if(kvp.Value == null) return false; 91 else if(!kvp.Value.CanCloseWithoutDataLoss) 92 return false; 93 } 94 } 95 96 return true; 97 } 98 } 99 100 public static Form TopWindow 101 { 102 get 103 { 104 lock(g_oSyncRoot) 105 { 106 int n = g_vWindows.Count; 107 if(n > 0) return g_vWindows[n - 1].Key; 108 } 109 110 return null; 111 } 112 } 113 114 public static event EventHandler<GwmWindowEventArgs> WindowAdded; 115 public static event EventHandler<GwmWindowEventArgs> WindowRemoved; 116 AddWindow(Form form)117 public static void AddWindow(Form form) 118 { 119 AddWindow(form, null); 120 } 121 AddWindow(Form form, IGwmWindow wnd)122 public static void AddWindow(Form form, IGwmWindow wnd) 123 { 124 if(form == null) { Debug.Assert(false); throw new ArgumentNullException("form"); } 125 126 KeyValuePair<Form, IGwmWindow> kvp = new KeyValuePair<Form, IGwmWindow>( 127 form, wnd); 128 129 lock(g_oSyncRoot) 130 { 131 Debug.Assert(g_vWindows.IndexOf(kvp) < 0); 132 g_vWindows.Add(kvp); 133 } 134 135 // The control box must be enabled, otherwise DPI scaling 136 // doesn't work due to a .NET bug: 137 // https://connect.microsoft.com/VisualStudio/feedback/details/694242/difference-dpi-let-the-button-cannot-appear-completely-while-remove-the-controlbox-for-the-form 138 // https://social.msdn.microsoft.com/Forums/en-US/winforms/thread/67407313-8cb2-42b4-afb9-8be816f0a601/ 139 Debug.Assert(form.ControlBox); 140 141 form.TopMost = Program.Config.MainWindow.AlwaysOnTop; 142 // Form formParent = form.ParentForm; 143 // if(formParent != null) form.TopMost = formParent.TopMost; 144 // else { Debug.Assert(false); } 145 146 // form.Font = new System.Drawing.Font(System.Drawing.SystemFonts.MessageBoxFont.Name, 12.0f); 147 148 CustomizeForm(form); 149 150 MonoWorkarounds.ApplyTo(form); 151 152 Debug.Assert(!(form is MainForm)); // MainForm calls the following itself 153 CustomizeFormHandleCreated(form, true, true); 154 155 if(GlobalWindowManager.WindowAdded != null) 156 GlobalWindowManager.WindowAdded(null, new GwmWindowEventArgs( 157 form, wnd)); 158 } 159 AddDialog(CommonDialog dlg)160 public static void AddDialog(CommonDialog dlg) 161 { 162 Debug.Assert(dlg != null); 163 if(dlg == null) throw new ArgumentNullException("dlg"); 164 165 lock(g_oSyncRoot) { g_vDialogs.Add(dlg); } 166 } 167 RemoveWindow(Form form)168 public static void RemoveWindow(Form form) 169 { 170 if(form == null) { Debug.Assert(false); throw new ArgumentNullException("form"); } 171 172 lock(g_oSyncRoot) 173 { 174 for(int i = 0; i < g_vWindows.Count; ++i) 175 { 176 if(g_vWindows[i].Key == form) 177 { 178 if(GlobalWindowManager.WindowRemoved != null) 179 GlobalWindowManager.WindowRemoved(null, new GwmWindowEventArgs( 180 form, g_vWindows[i].Value)); 181 182 MonoWorkarounds.Release(form); 183 184 Debug.Assert(!(form is MainForm)); // MainForm calls the following itself 185 CustomizeFormHandleCreated(form, false, false); 186 187 #if DEBUG 188 DebugClose(form); 189 #endif 190 191 g_vWindows.RemoveAt(i); 192 return; 193 } 194 } 195 } 196 197 Debug.Assert(false); // Window not found 198 } 199 RemoveDialog(CommonDialog dlg)200 public static void RemoveDialog(CommonDialog dlg) 201 { 202 Debug.Assert(dlg != null); 203 if(dlg == null) throw new ArgumentNullException("dlg"); 204 205 lock(g_oSyncRoot) 206 { 207 Debug.Assert(g_vDialogs.IndexOf(dlg) >= 0); 208 g_vDialogs.Remove(dlg); 209 } 210 } 211 CloseAllWindows()212 public static void CloseAllWindows() 213 { 214 Debug.Assert(GlobalWindowManager.CanCloseAllWindows); 215 216 KeyValuePair<Form, IGwmWindow>[] vWindows; 217 lock(g_oSyncRoot) { vWindows = g_vWindows.ToArray(); } 218 Array.Reverse(vWindows); // Close windows in reverse order 219 220 foreach(KeyValuePair<Form, IGwmWindow> kvp in vWindows) 221 { 222 if(kvp.Value == null) continue; 223 else if(kvp.Value.CanCloseWithoutDataLoss) 224 { 225 if(kvp.Key.InvokeRequired) 226 kvp.Key.Invoke(new CloseFormDelegate( 227 GlobalWindowManager.CloseForm), kvp.Key); 228 else CloseForm(kvp.Key); 229 230 Application.DoEvents(); 231 } 232 } 233 } 234 CloseFormDelegate(Form f)235 private delegate void CloseFormDelegate(Form f); 236 CloseForm(Form f)237 private static void CloseForm(Form f) 238 { 239 try 240 { 241 f.DialogResult = DialogResult.Cancel; 242 f.Close(); 243 } 244 catch(Exception) { Debug.Assert(false); } 245 } 246 HasWindow(IntPtr hWnd)247 public static bool HasWindow(IntPtr hWnd) 248 { 249 if(hWnd == IntPtr.Zero) { Debug.Assert(false); return false; } 250 251 lock(g_oSyncRoot) 252 { 253 foreach(KeyValuePair<Form, IGwmWindow> kvp in g_vWindows) 254 { 255 if(kvp.Key.Handle == hWnd) return true; 256 } 257 } 258 259 return false; 260 } 261 HasWindowMW(IntPtr hWnd)262 internal static bool HasWindowMW(IntPtr hWnd) 263 { 264 if(hWnd == IntPtr.Zero) { Debug.Assert(false); return false; } 265 266 if(hWnd == Program.GetSafeMainWindowHandle()) return true; 267 return HasWindow(hWnd); 268 } 269 ActivateTopWindow()270 internal static bool ActivateTopWindow() 271 { 272 try 273 { 274 Form f = GlobalWindowManager.TopWindow; 275 if(f == null) return false; 276 277 f.Activate(); 278 return true; 279 } 280 catch(Exception) { Debug.Assert(false); } 281 282 return false; 283 } 284 CustomizeForm(Form f)285 private static void CustomizeForm(Form f) 286 { 287 CustomizeControl(f); 288 289 try 290 { 291 const string strForms = "KeePass.Forms."; 292 Debug.Assert(typeof(PwEntryForm).FullName.StartsWith(strForms)); 293 294 if(f.GetType().FullName.StartsWith(strForms) && 295 (f.FormBorderStyle == FormBorderStyle.FixedDialog)) 296 UIUtil.RemoveBannerIfNecessary(f); 297 } 298 catch(Exception) { Debug.Assert(false); } 299 } 300 CustomizeControl(Control c)301 public static void CustomizeControl(Control c) 302 { 303 if(Program.DesignMode) return; 304 305 if(UISystemFonts.OverrideUIFont) 306 { 307 Font font = UISystemFonts.DefaultFont; 308 if(font != null) CustomizeFont(c, font); 309 } 310 } 311 CustomizeFont(Control c, Font font)312 private static void CustomizeFont(Control c, Font font) 313 { 314 if((c is Form) || (c is ToolStrip) || (c is ContextMenuStrip)) 315 c.Font = font; 316 317 foreach(Control cSub in c.Controls) 318 CustomizeFont(cSub, font); 319 320 if(c.ContextMenuStrip != null) 321 CustomizeFont(c.ContextMenuStrip, font); 322 } 323 CustomizeFormHandleCreated(Form f, bool? obSubscribe, bool bNow)324 internal static void CustomizeFormHandleCreated(Form f, 325 bool? obSubscribe, bool bNow) 326 { 327 if(f == null) { Debug.Assert(false); return; } 328 329 if(obSubscribe.HasValue) 330 { 331 if(obSubscribe.Value) 332 f.HandleCreated += GlobalWindowManager.OnFormHandleCreated; 333 else f.HandleCreated -= GlobalWindowManager.OnFormHandleCreated; 334 } 335 336 if(bNow) OnFormHandleCreated(f, EventArgs.Empty); 337 } 338 339 private static bool g_bDisplayAffChanged = false; OnFormHandleCreated(object sender, EventArgs e)340 private static void OnFormHandleCreated(object sender, EventArgs e) 341 { 342 try 343 { 344 Form f = (sender as Form); 345 if(f == null) { Debug.Assert(false); return; } 346 347 IntPtr hWnd = f.Handle; 348 if(hWnd == IntPtr.Zero) { Debug.Assert(false); return; } 349 350 if(WinUtil.IsAtLeastWindows7) 351 { 352 bool bPrvSC = Program.Config.Security.PreventScreenCapture; 353 if(bPrvSC || g_bDisplayAffChanged) 354 { 355 NativeMethods.SetWindowDisplayAffinity(hWnd, (bPrvSC ? 356 NativeMethods.WDA_MONITOR : NativeMethods.WDA_NONE)); 357 g_bDisplayAffChanged = true; // Set WDA_NONE in future calls 358 } 359 } 360 } 361 catch(Exception) { Debug.Assert(false); } 362 } 363 364 #if DEBUG DebugClose(Control c)365 private static void DebugClose(Control c) 366 { 367 if(c == null) { Debug.Assert(false); return; } 368 369 List<ImageList> lInv = new List<ImageList>(); 370 lInv.Add(Program.MainForm.ClientIcons); 371 372 ListView lv = (c as ListView); 373 if(lv != null) 374 { 375 // Image list properties must be set to null manually 376 // when closing, otherwise we get a memory leak 377 // (because the image list holds event handlers to 378 // the list) 379 Debug.Assert(!lInv.Contains(lv.LargeImageList)); 380 Debug.Assert(!lInv.Contains(lv.SmallImageList)); 381 } 382 383 TreeView tv = (c as TreeView); 384 if(tv != null) 385 { 386 Debug.Assert(!lInv.Contains(tv.ImageList)); // See above 387 } 388 389 TabControl tc = (c as TabControl); 390 if(tc != null) 391 { 392 Debug.Assert(!lInv.Contains(tc.ImageList)); // See above 393 } 394 395 ToolStrip ts = (c as ToolStrip); 396 if(ts != null) 397 { 398 Debug.Assert(!lInv.Contains(ts.ImageList)); // See above 399 } 400 401 Button btn = (c as Button); 402 if(btn != null) 403 { 404 Debug.Assert(!lInv.Contains(btn.ImageList)); // See above 405 } 406 407 foreach(Control cc in c.Controls) 408 DebugClose(cc); 409 } 410 #endif 411 InitializeForm(Form f)412 internal static void InitializeForm(Form f) 413 { 414 if(Program.DesignMode) return; 415 if(f == null) { Debug.Assert(false); return; } 416 417 try 418 { 419 Program.Translation.ApplyTo(f); 420 421 if(UIUtil.AccIsEnabled()) ReorderChildControlsByLoc(f); 422 } 423 catch(Exception) { Debug.Assert(false); } 424 } 425 ReorderChildControlsByLoc(Control c)426 private static void ReorderChildControlsByLoc(Control c) 427 { 428 if(c == null) { Debug.Assert(false); return; } 429 430 Control.ControlCollection cc = c.Controls; 431 if((cc == null) || (cc.Count == 0)) return; 432 433 c.SuspendLayout(); 434 try 435 { 436 List<KeyValuePair<Rectangle, Control>> l = 437 new List<KeyValuePair<Rectangle, Control>>(); 438 int cDockT = 0, cDockB = 0, cDockL = 0, cDockR = 0, cDockF = 0; 439 440 foreach(Control cSub in cc) 441 { 442 if((cSub == null) || (cSub == c)) { Debug.Assert(false); continue; } 443 444 Rectangle rect = cSub.Bounds; 445 if((rect.Width < 0) || (rect.Height < 0)) { Debug.Assert(false); continue; } 446 447 switch(cSub.Dock) 448 { 449 case DockStyle.Top: 450 ++cDockT; break; 451 case DockStyle.Bottom: 452 ++cDockB; break; 453 case DockStyle.Left: 454 ++cDockL; break; 455 case DockStyle.Right: 456 ++cDockR; break; 457 case DockStyle.Fill: 458 ++cDockF; break; 459 default: break; 460 } 461 462 l.Add(new KeyValuePair<Rectangle, Control>(rect, cSub)); 463 } 464 465 // Reordering docked controls can move them visually 466 bool bDockMovePossible = ( 467 ((cDockT + cDockL + cDockF) >= 2) || // Top left 468 ((cDockT + cDockR + cDockF) >= 2) || // Top right 469 ((cDockB + cDockL + cDockF) >= 2) || // Bottom left 470 ((cDockB + cDockR + cDockF) >= 2)); // Bottom right 471 472 bool bIgnoreType = ((c is DataGridView) || (c is NumericUpDown) || 473 (c is SplitContainer) || (c is TabControl) || (c is ToolStrip)); 474 475 Debug.Assert(bIgnoreType || !cc.IsReadOnly); 476 if((l.Count >= 2) && !bDockMovePossible && !bIgnoreType && 477 !cc.IsReadOnly) 478 { 479 l.Sort(GlobalWindowManager.CompareByLoc); 480 481 for(int i = 0; i < l.Count; ++i) 482 cc.SetChildIndex(l[i].Value, i); 483 484 #if DEBUG 485 for(int i = 0; i < l.Count; ++i) 486 Debug.Assert(cc[i] == l[i].Value); 487 #endif 488 } 489 490 foreach(KeyValuePair<Rectangle, Control> kvp in l) 491 ReorderChildControlsByLoc(kvp.Value); 492 } 493 catch(Exception) { Debug.Assert(false); } 494 finally { c.ResumeLayout(); } 495 } 496 CompareByLoc(KeyValuePair<Rectangle, Control> kvpA, KeyValuePair<Rectangle, Control> kvpB)497 private static int CompareByLoc(KeyValuePair<Rectangle, Control> kvpA, 498 KeyValuePair<Rectangle, Control> kvpB) 499 { 500 Rectangle rectA = kvpA.Key, rectB = kvpB.Key; 501 bool bAB = (rectB.Y > (rectA.Y + (rectA.Height >> 1))); 502 bool bBA = (rectA.Y > (rectB.Y + (rectB.Height >> 1))); 503 504 if(bAB && bBA) { Debug.Assert(false); } // Compare by X 505 else if(bAB) return -1; 506 else if(bBA) return 1; 507 // else: they are on the same line, compare by X 508 509 return rectA.X.CompareTo(rectB.X); 510 } 511 } 512 } 513