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.Collections.Specialized;
23 using System.Diagnostics;
24 using System.Drawing;
25 using System.Drawing.Drawing2D;
26 using System.Drawing.Imaging;
27 using System.IO;
28 using System.Reflection;
29 using System.Text;
30 using System.Windows.Forms;
31 
32 using KeePass.Native;
33 using KeePass.Util;
34 
35 using KeePassLib;
36 using KeePassLib.Utility;
37 
38 using NativeLib = KeePassLib.Native.NativeLib;
39 
40 namespace KeePass.UI
41 {
42 	public static class DpiUtil
43 	{
44 		private const int StdDpi = 96;
45 
46 		private static bool m_bInitialized = false;
47 
48 		private static int m_nDpiX = StdDpi;
49 		private static int m_nDpiY = StdDpi;
50 
51 		private static double m_dScaleX = 1.0;
52 		public static double FactorX
53 		{
54 			get
55 			{
56 				EnsureInitialized();
57 				return m_dScaleX;
58 			}
59 		}
60 
61 		private static double m_dScaleY = 1.0;
62 		public static double FactorY
63 		{
64 			get
65 			{
66 				EnsureInitialized();
67 				return m_dScaleY;
68 			}
69 		}
70 
71 		public static bool ScalingRequired
72 		{
73 			get
74 			{
75 				if(Program.DesignMode) return false;
76 
77 				EnsureInitialized();
78 				return ((m_nDpiX != StdDpi) || (m_nDpiY != StdDpi));
79 			}
80 		}
81 
EnsureInitialized()82 		private static void EnsureInitialized()
83 		{
84 			if(m_bInitialized) return;
85 			if(NativeLib.IsUnix()) { m_bInitialized = true; return; }
86 
87 			try
88 			{
89 				IntPtr hDC = NativeMethods.GetDC(IntPtr.Zero);
90 				if(hDC != IntPtr.Zero)
91 				{
92 					m_nDpiX = NativeMethods.GetDeviceCaps(hDC,
93 						NativeMethods.LOGPIXELSX);
94 					m_nDpiY = NativeMethods.GetDeviceCaps(hDC,
95 						NativeMethods.LOGPIXELSY);
96 					if((m_nDpiX <= 0) || (m_nDpiY <= 0))
97 					{
98 						Debug.Assert(false);
99 						m_nDpiX = StdDpi;
100 						m_nDpiY = StdDpi;
101 					}
102 
103 					if(NativeMethods.ReleaseDC(IntPtr.Zero, hDC) != 1)
104 					{
105 						Debug.Assert(false);
106 					}
107 				}
108 				else { Debug.Assert(false); }
109 			}
110 			catch(Exception) { Debug.Assert(false); }
111 
112 			m_dScaleX = (double)m_nDpiX / (double)StdDpi;
113 			m_dScaleY = (double)m_nDpiY / (double)StdDpi;
114 
115 			m_bInitialized = true;
116 		}
117 
ConfigureProcess()118 		public static void ConfigureProcess()
119 		{
120 			Debug.Assert(!m_bInitialized); // Configure process before use
121 			if(NativeLib.IsUnix()) return;
122 
123 			// try
124 			// {
125 			//	ConfigurationManager.AppSettings.Set(
126 			//		"EnableWindowsFormsHighDpiAutoResizing", "true");
127 			// }
128 			// catch(Exception) { Debug.Assert(false); }
129 #if DEBUG
130 			// Ensure that the .config file enables high DPI features
131 			string strExeConfig = WinUtil.GetExecutable() + ".config";
132 			if(File.Exists(strExeConfig))
133 			{
134 				string strCM = "System.Configuration.ConfigurationManager, ";
135 				strCM += "System.Configuration, Version=";
136 				strCM += Environment.Version.Major.ToString() + ".0.0.0, ";
137 				strCM += "Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
138 
139 				Type tCM = Type.GetType(strCM, false);
140 				if(tCM != null)
141 				{
142 					PropertyInfo pi = tCM.GetProperty("AppSettings",
143 						(BindingFlags.Public | BindingFlags.Static));
144 					if(pi != null)
145 					{
146 						NameValueCollection nvc = (pi.GetValue(null, null) as
147 							NameValueCollection);
148 						if(nvc != null)
149 						{
150 							Debug.Assert(string.Equals(nvc.Get(
151 								"EnableWindowsFormsHighDpiAutoResizing"),
152 								"true", StrUtil.CaseIgnoreCmp));
153 						}
154 						else { Debug.Assert(false); }
155 					}
156 					else { Debug.Assert(false); }
157 				}
158 				else { Debug.Assert(false); } // Assembly should be loaded
159 			}
160 #endif
161 
162 			try
163 			{
164 				// SetProcessDPIAware is obsolete; use
165 				// SetProcessDpiAwareness on Windows 10 and higher
166 				if(WinUtil.IsAtLeastWindows10) // 8.1 partially
167 				{
168 					if(NativeMethods.SetProcessDpiAwareness(
169 						NativeMethods.ProcessDpiAwareness.SystemAware) < 0)
170 					{
171 						Debug.Assert(false);
172 					}
173 				}
174 				else if(WinUtil.IsAtLeastWindowsVista)
175 				{
176 					if(!NativeMethods.SetProcessDPIAware()) { Debug.Assert(false); }
177 				}
178 			}
179 			catch(Exception) { Debug.Assert(false); }
180 		}
181 
Configure(ToolStrip ts)182 		internal static void Configure(ToolStrip ts)
183 		{
184 			if(ts == null) { Debug.Assert(false); return; }
185 			if(!DpiUtil.ScalingRequired) return;
186 
187 			Size sz = ts.ImageScalingSize;
188 			if((sz.Width == 16) && (sz.Height == 16))
189 			{
190 				sz.Width = ScaleIntX(16);
191 				sz.Height = ScaleIntY(16);
192 
193 				ts.ImageScalingSize = sz;
194 			}
195 			else
196 			{
197 				Debug.Assert(((sz.Width == ScaleIntX(16)) &&
198 					(sz.Height == ScaleIntY(16))), sz.ToString());
199 			}
200 		}
201 
ScaleIntX(int i)202 		public static int ScaleIntX(int i)
203 		{
204 			EnsureInitialized();
205 			return (int)Math.Round((double)i * m_dScaleX);
206 		}
207 
ScaleIntY(int i)208 		public static int ScaleIntY(int i)
209 		{
210 			EnsureInitialized();
211 			return (int)Math.Round((double)i * m_dScaleY);
212 		}
213 
ScaleImage(Image img, bool bForceNewObject)214 		public static Image ScaleImage(Image img, bool bForceNewObject)
215 		{
216 			if(img == null) { Debug.Assert(false); return null; }
217 
218 			// EnsureInitialized(); // Done by ScaleIntX
219 
220 			int w = img.Width;
221 			int h = img.Height;
222 			int sw = ScaleIntX(w);
223 			int sh = ScaleIntY(h);
224 
225 			if((w == sw) && (h == sh) && !bForceNewObject)
226 				return img;
227 
228 			return GfxUtil.ScaleImage(img, sw, sh, ScaleTransformFlags.UIIcon);
229 		}
230 
ScaleToolStripItems(ToolStripItem[] vItems, int[] vWidths)231 		internal static void ScaleToolStripItems(ToolStripItem[] vItems,
232 			int[] vWidths)
233 		{
234 			if(vItems == null) { Debug.Assert(false); return; }
235 			if(vWidths == null) { Debug.Assert(false); return; }
236 			if(vItems.Length != vWidths.Length) { Debug.Assert(false); return; }
237 
238 			for(int i = 0; i < vItems.Length; ++i)
239 			{
240 				ToolStripItem tsi = vItems[i];
241 				if(tsi == null) { Debug.Assert(false); continue; }
242 
243 				int nWidth = vWidths[i];
244 				int nWidthScaled = ScaleIntX(nWidth);
245 				Debug.Assert(nWidth >= 0);
246 
247 				if(nWidth == nWidthScaled)
248 				{
249 					Debug.Assert(tsi.Width == nWidth);
250 					continue;
251 				}
252 
253 				try
254 				{
255 					int w = tsi.Width;
256 
257 					// .NET scales some ToolStripItems, some not
258 					Debug.Assert(((w == nWidth) || (w == nWidthScaled)),
259 						tsi.Name + ": w = " + w.ToString());
260 
261 					if(Math.Abs(w - nWidth) < Math.Abs(w - nWidthScaled))
262 						tsi.Width = nWidthScaled;
263 				}
264 				catch(Exception) { Debug.Assert(false); }
265 			}
266 		}
267 
GetIcon(PwDatabase pd, PwUuid pwUuid)268 		internal static Image GetIcon(PwDatabase pd, PwUuid pwUuid)
269 		{
270 			if(pd == null) { Debug.Assert(false); return null; }
271 			if(pwUuid == null) { Debug.Assert(false); return null; }
272 
273 			int w = ScaleIntX(16);
274 			int h = ScaleIntY(16);
275 
276 			return pd.GetCustomIcon(pwUuid, w, h);
277 		}
278 
279 		[Conditional("DEBUG")]
AssertUIImage(Image img)280 		internal static void AssertUIImage(Image img)
281 		{
282 #if DEBUG
283 			if(img == null) { Debug.Assert(false); return; }
284 
285 			EnsureInitialized();
286 
287 			try
288 			{
289 				// Windows XP scales images based on the DPI resolution
290 				// specified in the image file; thus ensure that the
291 				// image file does not specify a DPI resolution;
292 				// https://sourceforge.net/p/keepass/bugs/1487/
293 
294 				int d = (int)Math.Round(img.HorizontalResolution);
295 				Debug.Assert((d == 0) || (d == m_nDpiX));
296 
297 				d = (int)Math.Round(img.VerticalResolution);
298 				Debug.Assert((d == 0) || (d == m_nDpiY));
299 			}
300 			catch(Exception) { Debug.Assert(false); }
301 #endif
302 		}
303 	}
304 }
305