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