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.Diagnostics;
22 using System.Drawing;
23 using System.IO;
24 using System.Runtime.InteropServices;
25 using System.Security;
26 using System.Text;
27 using System.Windows.Forms;
28 
29 using KeePass.UI;
30 using KeePass.Util;
31 
32 using KeePassLib.Utility;
33 
34 using NativeLib = KeePassLib.Native.NativeLib;
35 
36 namespace KeePass.Native
37 {
38 	internal static partial class NativeMethods
39 	{
GetWindowText(IntPtr hWnd, bool bTrim)40 		internal static string GetWindowText(IntPtr hWnd, bool bTrim)
41 		{
42 			// cc may be greater than the actual length;
43 			// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowtextlengthw
44 			int cc = GetWindowTextLength(hWnd);
45 			if(cc <= 0) return string.Empty;
46 
47 			// StringBuilder sb = new StringBuilder(cc + 2);
48 			// int ccReal = GetWindowText(hWnd, sb, cc + 1);
49 			// if(ccReal <= 0) { Debug.Assert(false); return string.Empty; }
50 			// // The text isn't always NULL-terminated; trim garbage
51 			// if(ccReal < sb.Length)
52 			//	sb.Remove(ccReal, sb.Length - ccReal);
53 			// string strWindow = sb.ToString();
54 
55 			string strWindow;
56 			IntPtr p = IntPtr.Zero;
57 			try
58 			{
59 				int cbChar = Marshal.SystemDefaultCharSize;
60 				int cb = (cc + 2) * cbChar;
61 				p = Marshal.AllocCoTaskMem(cb);
62 				if(p == IntPtr.Zero) { Debug.Assert(false); return string.Empty; }
63 
64 				byte[] pbZero = new byte[cb];
65 				Marshal.Copy(pbZero, 0, p, cb);
66 
67 				int ccReal = GetWindowText(hWnd, p, cc + 1);
68 				if(ccReal <= 0) { Debug.Assert(false); return string.Empty; }
69 
70 				if(ccReal <= cc)
71 				{
72 					// Ensure correct termination (in case GetWindowText
73 					// copied too much)
74 					int ibZero = ccReal * cbChar;
75 					for(int i = 0; i < cbChar; ++i)
76 						Marshal.WriteByte(p, ibZero + i, 0);
77 				}
78 				else { Debug.Assert(false); return string.Empty; }
79 
80 				strWindow = (Marshal.PtrToStringAuto(p) ?? string.Empty);
81 			}
82 			finally { if(p != IntPtr.Zero) Marshal.FreeCoTaskMem(p); }
83 
84 			return (bTrim ? strWindow.Trim() : strWindow);
85 		}
86 
87 		/* internal static string GetWindowClassName(IntPtr hWnd)
88 		{
89 			try
90 			{
91 				StringBuilder sb = new StringBuilder(260);
92 
93 				if(GetClassName(hWnd, sb, 258) > 0)
94 					return sb.ToString();
95 				else { Debug.Assert(false); }
96 
97 				return string.Empty;
98 			}
99 			catch(Exception) { Debug.Assert(false); }
100 
101 			return null;
102 		} */
103 
GetForegroundWindowHandle()104 		internal static IntPtr GetForegroundWindowHandle()
105 		{
106 			if(!NativeLib.IsUnix())
107 				return GetForegroundWindow(); // Windows API
108 
109 			try
110 			{
111 				return new IntPtr(long.Parse(RunXDoTool(
112 					"getactivewindow").Trim()));
113 			}
114 			catch(Exception) { Debug.Assert(false); }
115 			return IntPtr.Zero;
116 		}
117 
118 		private static readonly char[] g_vWindowNL = new char[] { '\r', '\n' };
GetForegroundWindowInfo(out IntPtr hWnd, out string strWindowText, bool bTrimWindow)119 		internal static void GetForegroundWindowInfo(out IntPtr hWnd,
120 			out string strWindowText, bool bTrimWindow)
121 		{
122 			hWnd = GetForegroundWindowHandle();
123 
124 			if(!NativeLib.IsUnix()) // Windows
125 				strWindowText = GetWindowText(hWnd, bTrimWindow);
126 			else // Unix
127 			{
128 				strWindowText = RunXDoTool("getactivewindow getwindowname");
129 				if(!string.IsNullOrEmpty(strWindowText))
130 				{
131 					if(bTrimWindow) strWindowText = strWindowText.Trim();
132 					else strWindowText = strWindowText.Trim(g_vWindowNL);
133 				}
134 			}
135 		}
136 
IsWindowEx(IntPtr hWnd)137 		internal static bool IsWindowEx(IntPtr hWnd)
138 		{
139 			if(!NativeLib.IsUnix()) // Windows
140 				return IsWindow(hWnd);
141 
142 			return true;
143 		}
144 
GetWindowStyle(IntPtr hWnd)145 		internal static int GetWindowStyle(IntPtr hWnd)
146 		{
147 			return GetWindowLong(hWnd, GWL_STYLE);
148 		}
149 
GetClassLongPtrEx(IntPtr hWnd, int nIndex)150 		internal static IntPtr GetClassLongPtrEx(IntPtr hWnd, int nIndex)
151 		{
152 			if(IntPtr.Size == 4) return GetClassLong(hWnd, nIndex);
153 			return GetClassLongPtr(hWnd, nIndex);
154 		}
155 
SetForegroundWindowEx(IntPtr hWnd)156 		internal static bool SetForegroundWindowEx(IntPtr hWnd)
157 		{
158 			if(!NativeLib.IsUnix())
159 				return SetForegroundWindow(hWnd);
160 
161 			return (RunXDoTool("windowactivate " +
162 				hWnd.ToInt64().ToString()).Trim().Length == 0);
163 		}
164 
EnsureForegroundWindow(IntPtr hWnd)165 		internal static bool EnsureForegroundWindow(IntPtr hWnd)
166 		{
167 			if(!IsWindowEx(hWnd)) return false;
168 
169 			IntPtr hWndInit = GetForegroundWindowHandle();
170 
171 			if(!SetForegroundWindowEx(hWnd))
172 			{
173 				Debug.Assert(false);
174 				return false;
175 			}
176 
177 			int nStartMS = Environment.TickCount;
178 			while((Environment.TickCount - nStartMS) < 1000)
179 			{
180 				IntPtr h = GetForegroundWindowHandle();
181 				if(h == hWnd) return true;
182 
183 				// Some applications (like Microsoft Edge) have multiple
184 				// windows and automatically redirect the focus to other
185 				// windows, thus also break when a different window gets
186 				// focused (except when h is zero, which can occur while
187 				// the focus transfer occurs)
188 				if((h != IntPtr.Zero) && (h != hWndInit)) return true;
189 
190 				Application.DoEvents();
191 			}
192 
193 			return false;
194 		}
195 
196 		// Workaround for .NET/Windows TopMost/WS_EX_TOPMOST desynchronization bug;
197 		// https://sourceforge.net/p/keepass/discussion/329220/thread/d45a3b38e8/
SyncTopMost(Form f)198 		internal static void SyncTopMost(Form f)
199 		{
200 			if(f == null) { Debug.Assert(false); return; }
201 			if(NativeLib.IsUnix()) return;
202 
203 			try
204 			{
205 				if(!f.TopMost) return; // Managed state
206 
207 				IntPtr h = f.Handle;
208 				if(h == IntPtr.Zero) return;
209 
210 				int s = GetWindowLong(h, GWL_EXSTYLE); // Unmanaged state
211 				if((s & WS_EX_TOPMOST) == 0)
212 				{
213 					f.TopMost = true; // Calls SetWindowPos (if TopLevel)
214 #if DEBUG
215 					Trace.WriteLine("Synchronized TopMost/WS_EX_TOPMOST.");
216 #endif
217 				}
218 			}
219 			catch(Exception) { Debug.Assert(false); }
220 		}
221 
FindWindow(string strTitle)222 		internal static IntPtr FindWindow(string strTitle)
223 		{
224 			if(strTitle == null) { Debug.Assert(false); return IntPtr.Zero; }
225 
226 			if(!NativeLib.IsUnix())
227 				return FindWindowEx(IntPtr.Zero, IntPtr.Zero, null, strTitle);
228 
229 			// Not --onlyvisible (due to not finding minimized windows)
230 			string str = RunXDoTool("search --name \"" + strTitle + "\"").Trim();
231 			if(str.Length == 0) return IntPtr.Zero;
232 
233 			long l;
234 			if(long.TryParse(str, out l)) return new IntPtr(l);
235 			return IntPtr.Zero;
236 		}
237 
LoseFocus(Form fCurrent, bool bSkipOwnWindows)238 		internal static bool LoseFocus(Form fCurrent, bool bSkipOwnWindows)
239 		{
240 			if(NativeLib.IsUnix()) return LoseFocusUnix(fCurrent);
241 
242 			try
243 			{
244 				IntPtr hWnd = ((fCurrent != null) ? fCurrent.Handle : IntPtr.Zero);
245 
246 				while(true)
247 				{
248 					IntPtr hWndPrev = hWnd;
249 					hWnd = GetWindow(hWnd, GW_HWNDNEXT);
250 
251 					if(hWnd == IntPtr.Zero) return false;
252 					if(hWnd == hWndPrev) { Debug.Assert(false); return false; }
253 
254 					int nStyle = GetWindowStyle(hWnd);
255 					if((nStyle & WS_VISIBLE) == 0) continue;
256 
257 					if(GetWindowTextLength(hWnd) == 0) continue;
258 
259 					if(bSkipOwnWindows && GlobalWindowManager.HasWindowMW(hWnd))
260 						continue;
261 
262 					// Skip the taskbar window (required for Windows 7,
263 					// when the target window is the only other window
264 					// in the taskbar)
265 					if(IsTaskBar(hWnd)) continue;
266 
267 					break;
268 				}
269 
270 				Debug.Assert(GetWindowText(hWnd, true) != "Start");
271 				return EnsureForegroundWindow(hWnd);
272 			}
273 			catch(Exception) { Debug.Assert(false); }
274 
275 			return false;
276 		}
277 
IsTaskBar(IntPtr hWnd)278 		internal static bool IsTaskBar(IntPtr hWnd)
279 		{
280 			Process p = null;
281 			try
282 			{
283 				string strText = GetWindowText(hWnd, true);
284 				if(strText == null) return false;
285 				if(!strText.Equals("Start", StrUtil.CaseIgnoreCmp)) return false;
286 
287 				uint uProcessId;
288 				NativeMethods.GetWindowThreadProcessId(hWnd, out uProcessId);
289 
290 				p = Process.GetProcessById((int)uProcessId);
291 				string strExe = UrlUtil.GetFileName(p.MainModule.FileName).Trim();
292 
293 				return strExe.Equals("Explorer.exe", StrUtil.CaseIgnoreCmp);
294 			}
295 			catch(Exception) { Debug.Assert(false); }
296 			finally
297 			{
298 				try { if(p != null) p.Dispose(); }
299 				catch(Exception) { Debug.Assert(false); }
300 			}
301 
302 			return false;
303 		}
304 
305 		/* internal static bool IsMetroWindow(IntPtr hWnd)
306 		{
307 			if(hWnd == IntPtr.Zero) { Debug.Assert(false); return false; }
308 			if(NativeLib.IsUnix() || !WinUtil.IsAtLeastWindows8)
309 				return false;
310 
311 			try
312 			{
313 				uint uProcessId;
314 				NativeMethods.GetWindowThreadProcessId(hWnd, out uProcessId);
315 				if(uProcessId == 0) { Debug.Assert(false); return false; }
316 
317 				IntPtr h = NativeMethods.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION,
318 					false, uProcessId);
319 				if(h == IntPtr.Zero) return false; // No assert
320 
321 				bool bRet = NativeMethods.IsImmersiveProcess(h);
322 
323 				NativeMethods.CloseHandle(h);
324 				return bRet;
325 			}
326 			catch(Exception) { Debug.Assert(false); }
327 
328 			return false;
329 		} */
330 
IsInvalidHandleValue(IntPtr p)331 		public static bool IsInvalidHandleValue(IntPtr p)
332 		{
333 			long h = p.ToInt64();
334 			if(h == -1) return true;
335 			if(h == 0xFFFFFFFF) return true;
336 
337 			return false;
338 		}
339 
GetHeaderHeight(ListView lv)340 		public static int GetHeaderHeight(ListView lv)
341 		{
342 			if(lv == null) { Debug.Assert(false); return 0; }
343 
344 			try
345 			{
346 				if((lv.View == View.Details) && (lv.HeaderStyle !=
347 					ColumnHeaderStyle.None) && (lv.Columns.Count > 0) &&
348 					!NativeLib.IsUnix())
349 				{
350 					IntPtr hHeader = NativeMethods.SendMessage(lv.Handle,
351 						NativeMethods.LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
352 					if(hHeader != IntPtr.Zero)
353 					{
354 						NativeMethods.RECT rect = new NativeMethods.RECT();
355 						if(NativeMethods.GetWindowRect(hHeader, ref rect))
356 							return (rect.Bottom - rect.Top);
357 						else { Debug.Assert(false); }
358 					}
359 					else { Debug.Assert(false); }
360 				}
361 			}
362 			catch(Exception) { Debug.Assert(false); }
363 
364 			return 0;
365 		}
366 
367 		// Workaround for only partially visible list view items
368 		/* public static void EnsureVisible(ListView lv, int nIndex, bool bPartialOK)
369 		{
370 			Debug.Assert(lv != null); if(lv == null) return;
371 			Debug.Assert(nIndex >= 0); if(nIndex < 0) return;
372 			Debug.Assert(nIndex < lv.Items.Count); if(nIndex >= lv.Items.Count) return;
373 
374 			int nPartialOK = (bPartialOK ? 1 : 0);
375 			try
376 			{
377 				NativeMethods.SendMessage(lv.Handle, LVM_ENSUREVISIBLE,
378 					new IntPtr(nIndex), new IntPtr(nPartialOK));
379 			}
380 			catch(Exception) { Debug.Assert(false); }
381 		} */
382 
GetScrollPosY(IntPtr hWnd)383 		public static int GetScrollPosY(IntPtr hWnd)
384 		{
385 			if(NativeLib.IsUnix()) return 0;
386 
387 			try
388 			{
389 				SCROLLINFO si = new SCROLLINFO();
390 				si.cbSize = (uint)Marshal.SizeOf(typeof(SCROLLINFO));
391 				si.fMask = (uint)ScrollInfoMask.SIF_POS;
392 
393 				if(GetScrollInfo(hWnd, (int)ScrollBarDirection.SB_VERT, ref si))
394 					return si.nPos;
395 
396 				Debug.Assert(false);
397 			}
398 			catch(Exception) { Debug.Assert(false); }
399 
400 			return 0;
401 		}
402 
Scroll(ListView lv, int dx, int dy)403 		public static void Scroll(ListView lv, int dx, int dy)
404 		{
405 			if(lv == null) throw new ArgumentNullException("lv");
406 			if(NativeLib.IsUnix()) return;
407 
408 			try { SendMessage(lv.Handle, LVM_SCROLL, (IntPtr)dx, (IntPtr)dy); }
409 			catch(Exception) { Debug.Assert(false); }
410 		}
411 
412 		/* public static void ScrollAbsY(IntPtr hWnd, int y)
413 		{
414 			try
415 			{
416 				SCROLLINFO si = new SCROLLINFO();
417 				si.cbSize = (uint)Marshal.SizeOf(typeof(SCROLLINFO));
418 				si.fMask = (uint)ScrollInfoMask.SIF_POS;
419 				si.nPos = y;
420 
421 				SetScrollInfo(hWnd, (int)ScrollBarDirection.SB_VERT, ref si, true);
422 			}
423 			catch(Exception) { Debug.Assert(false); }
424 		} */
425 
426 		/* public static void Scroll(IntPtr h, int dx, int dy)
427 		{
428 			if(h == IntPtr.Zero) { Debug.Assert(false); return; } // No throw
429 
430 			try
431 			{
432 				ScrollWindowEx(h, dx, dy, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero,
433 					IntPtr.Zero, SW_INVALIDATE);
434 			}
435 			catch(Exception) { Debug.Assert(false); }
436 		} */
437 
438 		/* internal static void ClearIconicBitmaps(IntPtr hWnd)
439 		{
440 			// TaskbarList.SetThumbnailClip(hWnd, new Rectangle(0, 0, 1, 1));
441 			// TaskbarList.SetThumbnailClip(hWnd, null);
442 
443 			try { DwmInvalidateIconicBitmaps(hWnd); }
444 			catch(Exception) { Debug.Assert(!WinUtil.IsAtLeastWindows7); }
445 		} */
446 
GetLastInputTime()447 		internal static uint? GetLastInputTime()
448 		{
449 			try
450 			{
451 				LASTINPUTINFO lii = new LASTINPUTINFO();
452 				lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
453 
454 				if(!GetLastInputInfo(ref lii)) { Debug.Assert(false); return null; }
455 
456 				return lii.dwTime;
457 			}
458 			catch(Exception)
459 			{
460 				Debug.Assert(NativeLib.IsUnix() || WinUtil.IsWindows9x);
461 			}
462 
463 			return null;
464 		}
465 
SHGetFileInfo(string strPath, int dxImg, int dyImg, out Image img, out string strDisplayName)466 		internal static bool SHGetFileInfo(string strPath, int dxImg, int dyImg,
467 			out Image img, out string strDisplayName)
468 		{
469 			img = null;
470 			strDisplayName = null;
471 
472 			try
473 			{
474 				SHFILEINFO fi = new SHFILEINFO();
475 
476 				IntPtr p = SHGetFileInfo(strPath, 0, ref fi, (uint)Marshal.SizeOf(typeof(
477 					SHFILEINFO)), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_DISPLAYNAME);
478 				if(p == IntPtr.Zero) return false;
479 
480 				if(fi.hIcon != IntPtr.Zero)
481 				{
482 					using(Icon ico = Icon.FromHandle(fi.hIcon)) // Doesn't take ownership
483 					{
484 						img = UIUtil.IconToBitmap(ico, dxImg, dyImg);
485 					}
486 
487 					if(!DestroyIcon(fi.hIcon)) { Debug.Assert(false); }
488 				}
489 
490 				strDisplayName = fi.szDisplayName;
491 				return true;
492 			}
493 			catch(Exception) { Debug.Assert(false); }
494 
495 			return false;
496 		}
497 
498 		/// <summary>
499 		/// Method for testing whether a file exists or not. Also
500 		/// supports NTFS alternate data streams.
501 		/// </summary>
502 		/// <param name="strFilePath">Path of the file or stream.</param>
503 		/// <returns><c>true</c> if the file exists.</returns>
FileExists(string strFilePath)504 		public static bool FileExists(string strFilePath)
505 		{
506 			if(strFilePath == null) throw new ArgumentNullException("strFilePath");
507 
508 			try
509 			{
510 				// https://sourceforge.net/p/keepass/discussion/329221/thread/65244cc9/
511 				if(!NativeLib.IsUnix())
512 					return (GetFileAttributes(strFilePath) != INVALID_FILE_ATTRIBUTES);
513 			}
514 			catch(Exception) { Debug.Assert(false); }
515 
516 			// Fallback to .NET method (for Unix-like systems)
517 			try { return File.Exists(strFilePath); }
518 			catch(Exception) { Debug.Assert(false); } // Invalid path
519 
520 			return false;
521 		}
522 
523 		/* internal static LVGROUP GetGroupInfoByIndex(ListView lv, uint uIndex)
524 		{
525 			if(lv == null) throw new ArgumentNullException("lv");
526 			if(uIndex >= (uint)lv.Groups.Count)
527 				throw new ArgumentOutOfRangeException("uIndex");
528 
529 			const int nStrLen = 1024;
530 
531 			LVGROUP g = new LVGROUP();
532 			g.cbSize = (uint)Marshal.SizeOf(typeof(LVGROUP));
533 
534 			g.mask = ...;
535 
536 			g.pszHeader = new StringBuilder(nStrLen);
537 			g.cchHeader = nStrLen - 1;
538 			g.pszFooter = new StringBuilder(nStrLen);
539 			g.cchFooter = nStrLen - 1;
540 			g.pszSubtitle = new StringBuilder(nStrLen);
541 			g.cchSubtitle = (uint)(nStrLen - 1);
542 			g.pszTask = new StringBuilder(nStrLen);
543 			g.cchTask = (uint)(nStrLen - 1);
544 			g.pszDescriptionTop = new StringBuilder(nStrLen);
545 			g.cchDescriptionTop = (uint)(nStrLen - 1);
546 			g.pszDescriptionBottom = new StringBuilder(nStrLen);
547 			g.cchDescriptionBottom = (uint)(nStrLen - 1);
548 			g.pszSubsetTitle = new StringBuilder(nStrLen);
549 			g.cchSubsetTitle = (uint)(nStrLen - 1);
550 
551 			SendMessageLVGroup(lv.Handle, LVM_GETGROUPINFOBYINDEX,
552 				new IntPtr((int)uIndex), ref g);
553 			return g;
554 		} */
555 
556 		/* internal static uint GetGroupStateByIndex(ListView lv, uint uIndex,
557 			uint uStateMask, out int iGroupID)
558 		{
559 			if(lv == null) throw new ArgumentNullException("lv");
560 			if(uIndex >= (uint)lv.Groups.Count)
561 				throw new ArgumentOutOfRangeException("uIndex");
562 
563 			LVGROUP g = new LVGROUP();
564 			g.cbSize = (uint)Marshal.SizeOf(typeof(LVGROUP));
565 
566 			g.mask = (LVGF_STATE | LVGF_GROUPID);
567 			g.stateMask = uStateMask;
568 
569 			SendMessageLVGroup(lv.Handle, LVM_GETGROUPINFOBYINDEX,
570 				new IntPtr((int)uIndex), ref g);
571 
572 			iGroupID = g.iGroupId;
573 			return g.state;
574 		}
575 
576 		internal static void SetGroupState(ListView lv, int iGroupID,
577 			uint uStateMask, uint uState)
578 		{
579 			if(lv == null) throw new ArgumentNullException("lv");
580 
581 			LVGROUP g = new LVGROUP();
582 			g.cbSize = (uint)Marshal.SizeOf(typeof(LVGROUP));
583 
584 			g.mask = LVGF_STATE;
585 			g.stateMask = uStateMask;
586 			g.state = uState;
587 
588 			SendMessageLVGroup(lv.Handle, LVM_SETGROUPINFO,
589 				new IntPtr(iGroupID), ref g);
590 		} */
591 
592 		/* internal static int GetGroupIDByIndex(ListView lv, uint uIndex)
593 		{
594 			if(lv == null) { Debug.Assert(false); return 0; }
595 
596 			LVGROUP g = new LVGROUP();
597 			g.cbSize = (uint)Marshal.SizeOf(typeof(LVGROUP));
598 			g.AssertSize();
599 
600 			g.mask = NativeMethods.LVGF_GROUPID;
601 
602 			if(SendMessageLVGroup(lv.Handle, LVM_GETGROUPINFOBYINDEX,
603 				new IntPtr((int)uIndex), ref g) == IntPtr.Zero)
604 			{
605 				Debug.Assert(false);
606 			}
607 
608 			return g.iGroupId;
609 		}
610 
611 		internal static void SetGroupTask(ListView lv, int iGroupID,
612 			string strTask)
613 		{
614 			if(lv == null) { Debug.Assert(false); return; }
615 
616 			LVGROUP g = new LVGROUP();
617 			g.cbSize = (uint)Marshal.SizeOf(typeof(LVGROUP));
618 			g.AssertSize();
619 
620 			g.mask = LVGF_TASK;
621 
622 			g.pszTask = strTask;
623 			g.cchTask = (uint)((strTask != null) ? strTask.Length : 0);
624 
625 			if(SendMessageLVGroup(lv.Handle, LVM_SETGROUPINFO,
626 				new IntPtr(iGroupID), ref g) == (new IntPtr(-1)))
627 			{
628 				Debug.Assert(false);
629 			}
630 		} */
631 
632 		/* internal static void SetListViewGroupInfo(ListView lv, int iGroupID,
633 			string strTask, bool? obCollapsible)
634 		{
635 			if(lv == null) { Debug.Assert(false); return; }
636 			if(!WinUtil.IsAtLeastWindowsVista) return;
637 
638 			LVGROUP g = new LVGROUP();
639 			g.cbSize = (uint)Marshal.SizeOf(typeof(LVGROUP));
640 			g.AssertSize();
641 
642 			if(strTask != null)
643 			{
644 				g.mask |= LVGF_TASK;
645 
646 				g.pszTask = strTask;
647 				g.cchTask = (uint)strTask.Length;
648 			}
649 
650 			if(obCollapsible.HasValue)
651 			{
652 				g.mask |= LVGF_STATE;
653 
654 				g.stateMask = LVGS_COLLAPSIBLE;
655 				g.state = (obCollapsible.Value ? LVGS_COLLAPSIBLE : 0);
656 			}
657 
658 			if(g.mask == 0) return;
659 			if(SendMessageLVGroup(lv.Handle, LVM_SETGROUPINFO,
660 				new IntPtr(iGroupID), ref g) == (new IntPtr(-1)))
661 			{
662 				Debug.Assert(false);
663 			}
664 		}
665 
666 		internal static int GetListViewGroupID(ListViewGroup lvg)
667 		{
668 			if(lvg == null) { Debug.Assert(false); return -1; }
669 
670 			Type t = typeof(ListViewGroup);
671 			PropertyInfo pi = t.GetProperty("ID", (BindingFlags.Instance |
672 				BindingFlags.NonPublic));
673 			if(pi == null) { Debug.Assert(false); return -1; }
674 			if(pi.PropertyType != typeof(int)) { Debug.Assert(false); return -1; }
675 
676 			return (int)pi.GetValue(lvg, null);
677 		} */
678 
GetDesktopName(IntPtr hDesk, out string strAnsi, out string strUni)679 		private static bool GetDesktopName(IntPtr hDesk, out string strAnsi,
680 			out string strUni)
681 		{
682 			strAnsi = null;
683 			strUni = null;
684 
685 			const uint cbZ = 12; // Minimal number of terminating zeros
686 			const uint uBufSize = 64 + cbZ;
687 			IntPtr pBuf = Marshal.AllocCoTaskMem((int)uBufSize);
688 			byte[] pbZero = new byte[uBufSize];
689 			Marshal.Copy(pbZero, 0, pBuf, pbZero.Length);
690 
691 			try
692 			{
693 				uint uReqSize = uBufSize - cbZ;
694 				bool bSuccess = GetUserObjectInformation(hDesk, 2, pBuf,
695 					uBufSize - cbZ, ref uReqSize);
696 				if(uReqSize > (uBufSize - cbZ))
697 				{
698 					Marshal.FreeCoTaskMem(pBuf);
699 					pBuf = Marshal.AllocCoTaskMem((int)(uReqSize + cbZ));
700 					pbZero = new byte[uReqSize + cbZ];
701 					Marshal.Copy(pbZero, 0, pBuf, pbZero.Length);
702 
703 					bSuccess = GetUserObjectInformation(hDesk, 2, pBuf,
704 						uReqSize, ref uReqSize);
705 					Debug.Assert((uReqSize + cbZ) == (uint)pbZero.Length);
706 				}
707 
708 				if(bSuccess)
709 				{
710 					try { strAnsi = Marshal.PtrToStringAnsi(pBuf).Trim(); }
711 					catch(Exception) { }
712 
713 					try { strUni = Marshal.PtrToStringUni(pBuf).Trim(); }
714 					catch(Exception) { }
715 
716 					return true;
717 				}
718 			}
719 			finally { Marshal.FreeCoTaskMem(pBuf); }
720 
721 			Debug.Assert(false);
722 			return false;
723 		}
724 
725 		// The GetUserObjectInformation function apparently returns the
726 		// desktop name using ANSI encoding even on Windows 7 systems.
727 		// As the encoding is not documented, we test both ANSI and
728 		// Unicode versions of the name.
DesktopNameContains(IntPtr hDesk, string strName)729 		internal static bool? DesktopNameContains(IntPtr hDesk, string strName)
730 		{
731 			if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; }
732 
733 			string strAnsi, strUni;
734 			if(!GetDesktopName(hDesk, out strAnsi, out strUni)) return null;
735 			if((strAnsi == null) && (strUni == null)) return null;
736 
737 			try
738 			{
739 				if((strAnsi != null) && (strAnsi.IndexOf(strName,
740 					StringComparison.OrdinalIgnoreCase) >= 0))
741 					return true;
742 			}
743 			catch(Exception) { Debug.Assert(false); }
744 
745 			try
746 			{
747 				if((strUni != null) && (strUni.IndexOf(strName,
748 					StringComparison.OrdinalIgnoreCase) >= 0))
749 					return true;
750 			}
751 			catch(Exception) { Debug.Assert(false); }
752 
753 			return false;
754 		}
755 
IsKeyDownMessage(ref Message m)756 		private static bool? IsKeyDownMessage(ref Message m)
757 		{
758 			if(m.Msg == NativeMethods.WM_KEYDOWN) return true;
759 			if(m.Msg == NativeMethods.WM_KEYUP) return false;
760 			if(m.Msg == NativeMethods.WM_SYSKEYDOWN) return true;
761 			if(m.Msg == NativeMethods.WM_SYSKEYUP) return false;
762 			return null;
763 		}
764 
GetKeyMessageState(ref Message m, out bool bDown)765 		internal static bool GetKeyMessageState(ref Message m, out bool bDown)
766 		{
767 			bool? obKeyDown = IsKeyDownMessage(ref m);
768 			if(!obKeyDown.HasValue)
769 			{
770 				Debug.Assert(false);
771 				bDown = false;
772 				return false;
773 			}
774 
775 			bDown = obKeyDown.Value;
776 			return true;
777 		}
778 
779 		/* internal static string GetKeyboardLayoutNameEx()
780 		{
781 			StringBuilder sb = new StringBuilder(KL_NAMELENGTH + 1);
782 			if(GetKeyboardLayoutName(sb))
783 			{
784 				Debug.Assert(sb.Length == (KL_NAMELENGTH - 1));
785 				return sb.ToString();
786 			}
787 			else { Debug.Assert(false); }
788 
789 			return null;
790 		} */
791 
792 		/// <summary>
793 		/// PRIMARYLANGID macro.
794 		/// </summary>
GetPrimaryLangID(ushort uLangID)795 		internal static ushort GetPrimaryLangID(ushort uLangID)
796 		{
797 			return (ushort)(uLangID & 0x3FFU);
798 		}
799 
MapVirtualKey3(uint uCode, uint uMapType, IntPtr hKL)800 		internal static uint MapVirtualKey3(uint uCode, uint uMapType, IntPtr hKL)
801 		{
802 			if(hKL == IntPtr.Zero) return MapVirtualKey(uCode, uMapType);
803 			return MapVirtualKeyEx(uCode, uMapType, hKL);
804 		}
805 
VkKeyScan3(char ch, IntPtr hKL)806 		internal static ushort VkKeyScan3(char ch, IntPtr hKL)
807 		{
808 			if(hKL == IntPtr.Zero) return VkKeyScan(ch);
809 			return VkKeyScanEx(ch, hKL);
810 		}
811 
812 		/// <returns>
813 		/// Null, if there exists no translation or an error occured.
814 		/// An empty string, if the key is a dead key.
815 		/// Otherwise, the generated Unicode string (typically 1 character,
816 		/// but can be more when a dead key is stored in the keyboard layout).
817 		/// </returns>
ToUnicode3(int vKey, byte[] pbKeyState, IntPtr hKL)818 		internal static string ToUnicode3(int vKey, byte[] pbKeyState, IntPtr hKL)
819 		{
820 			const int cbState = 256;
821 			IntPtr pState = IntPtr.Zero;
822 			try
823 			{
824 				uint uScanCode = MapVirtualKey3((uint)vKey, MAPVK_VK_TO_VSC, hKL);
825 
826 				pState = Marshal.AllocHGlobal(cbState);
827 				if(pState == IntPtr.Zero) { Debug.Assert(false); return null; }
828 
829 				if(pbKeyState != null)
830 				{
831 					if(pbKeyState.Length == cbState)
832 						Marshal.Copy(pbKeyState, 0, pState, cbState);
833 					else { Debug.Assert(false); return null; }
834 				}
835 				else
836 				{
837 					// Windows' GetKeyboardState function does not return
838 					// the current virtual key array; as a workaround,
839 					// calling GetKeyState is mentioned sometimes, but
840 					// this doesn't work reliably either;
841 					// http://pinvoke.net/default.aspx/user32/GetKeyboardState.html
842 
843 					// GetKeyState(VK_SHIFT);
844 					// if(!GetKeyboardState(pState)) { Debug.Assert(false); return null; }
845 
846 					Debug.Assert(false);
847 					return null;
848 				}
849 
850 				const int cchUni = 30;
851 				StringBuilder sbUni = new StringBuilder(cchUni + 2);
852 
853 				int r;
854 				if(hKL == IntPtr.Zero)
855 					r = ToUnicode((uint)vKey, uScanCode, pState, sbUni,
856 						cchUni, 0);
857 				else
858 					r = ToUnicodeEx((uint)vKey, uScanCode, pState, sbUni,
859 						cchUni, 0, hKL);
860 
861 				if(r < 0) return string.Empty; // Dead key
862 				if(r == 0) return null; // No translation
863 
864 				string str = sbUni.ToString();
865 				if(string.IsNullOrEmpty(str)) { Debug.Assert(false); return null; }
866 
867 				// Extra characters may be returned, but are invalid
868 				// and should be ignored;
869 				// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646320.aspx
870 				if(r < str.Length) str = str.Substring(0, r);
871 
872 				return str;
873 			}
874 			catch(Exception) { Debug.Assert(false); }
875 			finally { if(pState != IntPtr.Zero) Marshal.FreeHGlobal(pState); }
876 
877 			return null;
878 		}
879 	}
880 }
881