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.Media; 24 using System.Security; 25 using System.Text; 26 using System.Text.RegularExpressions; 27 using System.Threading; 28 using System.Windows.Forms; 29 30 using KeePass.App; 31 using KeePass.Forms; 32 using KeePass.Native; 33 using KeePass.Resources; 34 using KeePass.UI; 35 using KeePass.Util.Spr; 36 37 using KeePassLib; 38 using KeePassLib.Collections; 39 using KeePassLib.Delegates; 40 using KeePassLib.Resources; 41 using KeePassLib.Security; 42 using KeePassLib.Utility; 43 44 using NativeLib = KeePassLib.Native.NativeLib; 45 46 namespace KeePass.Util 47 { 48 public sealed class AutoTypeEventArgs : EventArgs 49 { 50 private string m_strSeq; // Never null 51 public string Sequence 52 { 53 get { return m_strSeq; } 54 set 55 { 56 if(value == null) throw new ArgumentNullException("value"); 57 m_strSeq = value; 58 } 59 } 60 61 public bool SendObfuscated { get; set; } 62 public PwEntry Entry { get; private set; } 63 public PwDatabase Database { get; private set; } 64 AutoTypeEventArgs(string strSequence, bool bObfuscated, PwEntry pe, PwDatabase pd)65 public AutoTypeEventArgs(string strSequence, bool bObfuscated, PwEntry pe, 66 PwDatabase pd) 67 { 68 if(strSequence == null) throw new ArgumentNullException("strSequence"); 69 // pe may be null 70 71 m_strSeq = strSequence; 72 this.SendObfuscated = bObfuscated; 73 this.Entry = pe; 74 this.Database = pd; 75 } 76 } 77 78 public static class AutoType 79 { 80 private const int TargetActivationDelay = 100; 81 82 public static event EventHandler<AutoTypeEventArgs> FilterCompilePre; 83 public static event EventHandler<AutoTypeEventArgs> FilterSendPre; 84 public static event EventHandler<AutoTypeEventArgs> FilterSend; 85 86 public static event EventHandler<AutoTypeEventArgs> SendPost; 87 88 public static event EventHandler<SequenceQueryEventArgs> SequenceQueryPre; 89 public static event EventHandler<SequenceQueryEventArgs> SequenceQuery; 90 public static event EventHandler<SequenceQueryEventArgs> SequenceQueryPost; 91 92 public static event EventHandler<SequenceQueriesEventArgs> SequenceQueriesBegin; 93 public static event EventHandler<SequenceQueriesEventArgs> SequenceQueriesEnd; 94 95 private static int m_iEventID = 0; GetNextEventID()96 public static int GetNextEventID() 97 { 98 return Interlocked.Increment(ref m_iEventID); 99 } 100 InitStatic()101 internal static void InitStatic() 102 { 103 try 104 { 105 // SendKeys is not used anymore, thus the following is 106 // not required: 107 // // Enable new SendInput method; see 108 // // https://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.aspx 109 // ConfigurationManager.AppSettings.Set("SendKeys", "SendInput"); 110 } 111 catch(Exception) { Debug.Assert(false); } 112 } 113 IsMatchWindow(string strWindow, string strFilter)114 internal static bool IsMatchWindow(string strWindow, string strFilter) 115 { 116 if(strWindow == null) { Debug.Assert(false); return false; } 117 if(strFilter == null) { Debug.Assert(false); return false; } 118 119 Debug.Assert(NormalizeWindowText(strWindow) == strWindow); // Should be done by caller 120 string strF = NormalizeWindowText(strFilter); 121 122 int ccF = strF.Length; 123 if((ccF > 4) && (strF[0] == '/') && (strF[1] == '/') && 124 (strF[ccF - 2] == '/') && (strF[ccF - 1] == '/')) 125 { 126 try 127 { 128 string strRx = strF.Substring(2, ccF - 4); 129 return Regex.IsMatch(strWindow, strRx, RegexOptions.IgnoreCase); 130 } 131 catch(Exception) { return false; } 132 } 133 134 return StrUtil.SimplePatternMatch(strF, strWindow, StrUtil.CaseIgnoreCmp); 135 } 136 IsMatchSub(string strWindow, string strSub)137 private static bool IsMatchSub(string strWindow, string strSub) 138 { 139 if(strWindow == null) { Debug.Assert(false); return false; } 140 if(strSub == null) { Debug.Assert(false); return false; } 141 142 Debug.Assert(NormalizeWindowText(strWindow) == strWindow); // Should be done by caller 143 string strS = NormalizeWindowText(strSub); 144 145 // If strS is empty, return false, because strS is a substring 146 // that must occur, not a filter 147 if(strS.Length == 0) return false; 148 149 return (strWindow.IndexOf(strS, StrUtil.CaseIgnoreCmp) >= 0); 150 } 151 Execute(AutoTypeCtx ctx)152 private static bool Execute(AutoTypeCtx ctx) 153 { 154 if(ctx == null) { Debug.Assert(false); return false; } 155 156 string strSeq = ctx.Sequence; 157 PwEntry pweData = ctx.Entry; 158 if(pweData == null) { Debug.Assert(false); return false; } 159 160 if(!pweData.GetAutoTypeEnabled()) return false; 161 if(!AppPolicy.Try(AppPolicyId.AutoType)) return false; 162 163 if(NativeLib.IsUnix()) 164 { 165 if(!NativeMethods.TryXDoTool() && !NativeLib.IsWayland()) 166 { 167 MessageService.ShowWarning(KPRes.AutoTypeXDoToolRequired, 168 KPRes.PackageInstallHint); 169 return false; 170 } 171 } 172 173 PwDatabase pwDatabase = ctx.Database; 174 175 bool bObfuscate = (pweData.AutoType.ObfuscationOptions != 176 AutoTypeObfuscationOptions.None); 177 AutoTypeEventArgs args = new AutoTypeEventArgs(strSeq, bObfuscate, 178 pweData, pwDatabase); 179 180 if(AutoType.FilterCompilePre != null) AutoType.FilterCompilePre(null, args); 181 182 args.Sequence = SprEngine.Compile(args.Sequence, new SprContext( 183 pweData, pwDatabase, SprCompileFlags.All, true, false)); 184 185 // string strError = ValidateAutoTypeSequence(args.Sequence); 186 // if(!string.IsNullOrEmpty(strError)) 187 // { 188 // MessageService.ShowWarning(args.Sequence + 189 // MessageService.NewParagraph + strError); 190 // return false; 191 // } 192 193 Application.DoEvents(); 194 195 if(AutoType.FilterSendPre != null) AutoType.FilterSendPre(null, args); 196 if(AutoType.FilterSend != null) AutoType.FilterSend(null, args); 197 198 if(args.Sequence.Length > 0) 199 { 200 string strError = null; 201 try { SendInputEx.SendKeysWait(args.Sequence, args.SendObfuscated); } 202 catch(SecurityException exSec) { strError = exSec.Message; } 203 catch(Exception ex) 204 { 205 strError = args.Sequence + MessageService.NewParagraph + 206 ex.Message; 207 } 208 209 if(AutoType.SendPost != null) AutoType.SendPost(null, args); 210 211 if(!string.IsNullOrEmpty(strError)) 212 { 213 try 214 { 215 MainForm mfP = Program.MainForm; 216 if(mfP != null) 217 mfP.EnsureVisibleForegroundWindow(false, false); 218 } 219 catch(Exception) { Debug.Assert(false); } 220 221 MessageService.ShowWarning(strError); 222 } 223 } 224 225 pweData.Touch(false); 226 EntryUtil.ExpireTanEntryIfOption(pweData, pwDatabase); 227 228 MainForm mf = Program.MainForm; 229 if(mf != null) 230 { 231 // Always refresh entry list (e.g. {NEWPASSWORD} might 232 // have changed data) 233 mf.RefreshEntriesList(); 234 235 // SprEngine.Compile might have modified the database; 236 // pd.Modified is set by SprEngine 237 mf.UpdateUI(false, null, false, null, false, null, false); 238 239 if(Program.Config.MainWindow.MinimizeAfterAutoType && 240 mf.IsCommandTypeInvokable(null, MainForm.AppCommandType.Window)) 241 UIUtil.SetWindowState(mf, FormWindowState.Minimized); 242 } 243 244 return true; 245 } 246 PerformInternal(AutoTypeCtx ctx, string strWindow)247 private static bool PerformInternal(AutoTypeCtx ctx, string strWindow) 248 { 249 if(ctx == null) { Debug.Assert(false); return false; } 250 251 AutoTypeCtx ctxNew = ctx.Clone(); 252 253 if(Program.Config.Integration.AutoTypePrependInitSequenceForIE && 254 WinUtil.IsInternetExplorer7Window(strWindow)) 255 { 256 ctxNew.Sequence = @"{DELAY 50}1{DELAY 50}{BACKSPACE}" + 257 ctxNew.Sequence; 258 } 259 260 return AutoType.Execute(ctxNew); 261 } 262 GetSequencesForWindowBegin( IntPtr hWnd, string strWindow)263 private static SequenceQueriesEventArgs GetSequencesForWindowBegin( 264 IntPtr hWnd, string strWindow) 265 { 266 SequenceQueriesEventArgs e = new SequenceQueriesEventArgs( 267 GetNextEventID(), hWnd, strWindow); 268 269 if(AutoType.SequenceQueriesBegin != null) 270 AutoType.SequenceQueriesBegin(null, e); 271 272 return e; 273 } 274 GetSequencesForWindowEnd(SequenceQueriesEventArgs e)275 private static void GetSequencesForWindowEnd(SequenceQueriesEventArgs e) 276 { 277 if(AutoType.SequenceQueriesEnd != null) 278 AutoType.SequenceQueriesEnd(null, e); 279 } 280 281 // Multiple calls of this method are wrapped in 282 // GetSequencesForWindowBegin and GetSequencesForWindowEnd GetSequencesForWindow(PwEntry pwe, IntPtr hWnd, string strWindow, PwDatabase pdContext, int iEventID)283 private static List<string> GetSequencesForWindow(PwEntry pwe, 284 IntPtr hWnd, string strWindow, PwDatabase pdContext, int iEventID) 285 { 286 List<string> l = new List<string>(); 287 288 if(pwe == null) { Debug.Assert(false); return l; } 289 if(strWindow == null) { Debug.Assert(false); return l; } // May be empty 290 291 if(!pwe.GetAutoTypeEnabled()) return l; 292 293 SprContext sprCtx = new SprContext(pwe, pdContext, 294 SprCompileFlags.NonActive); 295 296 RaiseSequenceQueryEvent(AutoType.SequenceQueryPre, iEventID, 297 hWnd, strWindow, pwe, pdContext, l); 298 299 // Specifically defined sequences must match before the title, 300 // in order to allow selecting the first item as default one 301 foreach(AutoTypeAssociation a in pwe.AutoType.Associations) 302 { 303 string strFilter = SprEngine.Compile(a.WindowName, sprCtx); 304 if(IsMatchWindow(strWindow, strFilter)) 305 { 306 string strSeq = a.Sequence; 307 if(string.IsNullOrEmpty(strSeq)) 308 strSeq = pwe.GetAutoTypeSequence(); 309 AddSequence(l, strSeq); 310 } 311 } 312 313 RaiseSequenceQueryEvent(AutoType.SequenceQuery, iEventID, 314 hWnd, strWindow, pwe, pdContext, l); 315 316 if(Program.Config.Integration.AutoTypeMatchByTitle) 317 { 318 string strTitle = SprEngine.Compile(pwe.Strings.ReadSafe( 319 PwDefs.TitleField), sprCtx); 320 if(IsMatchSub(strWindow, strTitle)) 321 AddSequence(l, pwe.GetAutoTypeSequence()); 322 } 323 324 string strCmpUrl = null; // To cache the compiled URL 325 if(Program.Config.Integration.AutoTypeMatchByUrlInTitle) 326 { 327 strCmpUrl = SprEngine.Compile(pwe.Strings.ReadSafe( 328 PwDefs.UrlField), sprCtx); 329 if(IsMatchSub(strWindow, strCmpUrl)) 330 AddSequence(l, pwe.GetAutoTypeSequence()); 331 } 332 333 if(Program.Config.Integration.AutoTypeMatchByUrlHostInTitle) 334 { 335 if(strCmpUrl == null) 336 strCmpUrl = SprEngine.Compile(pwe.Strings.ReadSafe( 337 PwDefs.UrlField), sprCtx); 338 339 string strCleanUrl = StrUtil.RemovePlaceholders(strCmpUrl).Trim(); 340 string strHost = UrlUtil.GetHost(strCleanUrl); 341 342 if(strHost.StartsWith("www.", StrUtil.CaseIgnoreCmp) && 343 (strCleanUrl.StartsWith("http:", StrUtil.CaseIgnoreCmp) || 344 strCleanUrl.StartsWith("https:", StrUtil.CaseIgnoreCmp))) 345 strHost = strHost.Substring(4); 346 347 if(IsMatchSub(strWindow, strHost)) 348 AddSequence(l, pwe.GetAutoTypeSequence()); 349 } 350 351 if(Program.Config.Integration.AutoTypeMatchByTagInTitle) 352 { 353 foreach(string strTag in pwe.Tags) 354 { 355 if(IsMatchSub(strWindow, strTag)) 356 { 357 AddSequence(l, pwe.GetAutoTypeSequence()); 358 break; 359 } 360 } 361 } 362 363 RaiseSequenceQueryEvent(AutoType.SequenceQueryPost, iEventID, 364 hWnd, strWindow, pwe, pdContext, l); 365 366 return l; 367 } 368 RaiseSequenceQueryEvent( EventHandler<SequenceQueryEventArgs> f, int iEventID, IntPtr hWnd, string strWindow, PwEntry pwe, PwDatabase pdContext, List<string> lSeq)369 private static void RaiseSequenceQueryEvent( 370 EventHandler<SequenceQueryEventArgs> f, int iEventID, IntPtr hWnd, 371 string strWindow, PwEntry pwe, PwDatabase pdContext, 372 List<string> lSeq) 373 { 374 if(f == null) return; 375 376 SequenceQueryEventArgs e = new SequenceQueryEventArgs(iEventID, 377 hWnd, strWindow, pwe, pdContext); 378 f(null, e); 379 380 foreach(string strSeq in e.Sequences) 381 AddSequence(lSeq, strSeq); 382 } 383 AddSequence(List<string> lSeq, string strSeq)384 private static void AddSequence(List<string> lSeq, string strSeq) 385 { 386 if(strSeq == null) { Debug.Assert(false); return; } 387 388 string strCanSeq = CanonicalizeSeq(strSeq); 389 390 for(int i = 0; i < lSeq.Count; ++i) 391 { 392 string strCanEx = CanonicalizeSeq(lSeq[i]); 393 if(strCanEx.Equals(strCanSeq)) return; // Exists already 394 } 395 396 lSeq.Add(strSeq); // Non-canonical version 397 } 398 399 private const string StrBraceOpen = @"{1E1F63AB-2F63-4B60-ADBA-7F38B8D7778E}"; 400 private const string StrBraceClose = @"{34D698D7-CEBF-4AF0-87BF-DC1B1F5E95A0}"; CanonicalizeSeq(string strSeq)401 private static string CanonicalizeSeq(string strSeq) 402 { 403 // Preprocessing: balance braces 404 strSeq = strSeq.Replace(@"{{}", StrBraceOpen); 405 strSeq = strSeq.Replace(@"{}}", StrBraceClose); 406 407 StringBuilder sb = new StringBuilder(); 408 409 bool bInPlh = false; 410 for(int i = 0; i < strSeq.Length; ++i) 411 { 412 char ch = strSeq[i]; 413 414 if(ch == '{') bInPlh = true; 415 else if(ch == '}') bInPlh = false; 416 else if(bInPlh) ch = char.ToUpper(ch); 417 418 sb.Append(ch); 419 } 420 421 strSeq = sb.ToString(); 422 423 // Postprocessing: restore braces 424 strSeq = strSeq.Replace(StrBraceOpen, @"{{}"); 425 strSeq = strSeq.Replace(StrBraceClose, @"{}}"); 426 427 return strSeq; 428 } 429 IsValidAutoTypeWindow(IntPtr hWnd, bool bBeepIfNot)430 public static bool IsValidAutoTypeWindow(IntPtr hWnd, bool bBeepIfNot) 431 { 432 bool bValid = !GlobalWindowManager.HasWindowMW(hWnd); 433 434 if(!bValid && bBeepIfNot) SystemSounds.Beep.Play(); 435 436 return bValid; 437 } 438 PerformGlobal(List<PwDatabase> lSources, ImageList ilIcons)439 public static bool PerformGlobal(List<PwDatabase> lSources, 440 ImageList ilIcons) 441 { 442 return PerformGlobal(lSources, ilIcons, null); 443 } 444 PerformGlobal(List<PwDatabase> lSources, ImageList ilIcons, string strSeq)445 internal static bool PerformGlobal(List<PwDatabase> lSources, 446 ImageList ilIcons, string strSeq) 447 { 448 if(lSources == null) { Debug.Assert(false); return false; } 449 450 if(NativeLib.IsUnix()) 451 { 452 if(!NativeMethods.TryXDoTool(true) && !NativeLib.IsWayland()) 453 { 454 MessageService.ShowWarning(KPRes.AutoTypeXDoToolRequiredGlobalVer); 455 return false; 456 } 457 } 458 459 IntPtr hWnd; 460 string strWindow; 461 GetForegroundWindowInfo(out hWnd, out strWindow); 462 463 // if(string.IsNullOrEmpty(strWindow)) return false; 464 if(strWindow == null) { Debug.Assert(false); return false; } 465 if(!IsValidAutoTypeWindow(hWnd, true)) return false; 466 467 SequenceQueriesEventArgs evQueries = GetSequencesForWindowBegin( 468 hWnd, strWindow); 469 470 List<AutoTypeCtx> lCtxs = new List<AutoTypeCtx>(); 471 PwDatabase pdCurrent = null; 472 bool bExpCanMatch = Program.Config.Integration.AutoTypeExpiredCanMatch; 473 DateTime dtNow = DateTime.UtcNow; 474 475 EntryHandler eh = delegate(PwEntry pe) 476 { 477 if(!bExpCanMatch && pe.Expires && (pe.ExpiryTime <= dtNow)) 478 return true; // Ignore expired entries 479 480 List<string> lSeq = GetSequencesForWindow(pe, hWnd, strWindow, 481 pdCurrent, evQueries.EventID); 482 483 if(!string.IsNullOrEmpty(strSeq) && (lSeq.Count != 0)) 484 lCtxs.Add(new AutoTypeCtx(strSeq, pe, pdCurrent)); 485 else 486 { 487 foreach(string strSeqCand in lSeq) 488 { 489 lCtxs.Add(new AutoTypeCtx(strSeqCand, pe, pdCurrent)); 490 } 491 } 492 493 return true; 494 }; 495 496 foreach(PwDatabase pd in lSources) 497 { 498 if((pd == null) || !pd.IsOpen) continue; 499 pdCurrent = pd; 500 pd.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); 501 } 502 503 GetSequencesForWindowEnd(evQueries); 504 505 bool bForceDlg = Program.Config.Integration.AutoTypeAlwaysShowSelDialog; 506 507 if((lCtxs.Count >= 2) || bForceDlg) 508 { 509 AutoTypeCtxForm dlg = new AutoTypeCtxForm(); 510 dlg.InitEx(lCtxs, ilIcons); 511 512 bool bOK = (dlg.ShowDialog() == DialogResult.OK); 513 AutoTypeCtx ctx = (bOK ? dlg.SelectedCtx : null); 514 UIUtil.DestroyForm(dlg); 515 516 if(ctx != null) 517 { 518 try { NativeMethods.EnsureForegroundWindow(hWnd); } 519 catch(Exception) { Debug.Assert(false); } 520 521 int nActDelayMS = TargetActivationDelay; 522 string strWindowT = strWindow.Trim(); 523 524 // https://sourceforge.net/p/keepass/discussion/329220/thread/3681f343/ 525 // This apparently is only required here (after showing the 526 // auto-type entry selection dialog), not when using the 527 // context menu command in the main window 528 if(strWindowT.EndsWith("Microsoft Edge", StrUtil.CaseIgnoreCmp)) 529 { 530 // 700 skips the first 1-2 characters, 531 // 750 sometimes skips the first character 532 nActDelayMS = 1000; 533 } 534 535 // Allow target window to handle its activation 536 // (required by some applications, e.g. Edge) 537 Application.DoEvents(); 538 Thread.Sleep(nActDelayMS); 539 Application.DoEvents(); 540 541 AutoType.PerformInternal(ctx, strWindow); 542 } 543 } 544 else if(lCtxs.Count == 1) 545 AutoType.PerformInternal(lCtxs[0], strWindow); 546 547 return true; 548 } 549 550 [Obsolete] PerformIntoPreviousWindow(Form fCurrent, PwEntry pe)551 public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntry pe) 552 { 553 return PerformIntoPreviousWindow(fCurrent, pe, 554 Program.MainForm.DocumentManager.SafeFindContainerOf(pe), null); 555 } 556 PerformIntoPreviousWindow(Form fCurrent, PwEntry pe, PwDatabase pdContext)557 public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntry pe, 558 PwDatabase pdContext) 559 { 560 return PerformIntoPreviousWindow(fCurrent, pe, pdContext, null); 561 } 562 PerformIntoPreviousWindow(Form fCurrent, PwEntry pe, PwDatabase pdContext, string strSeq)563 public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntry pe, 564 PwDatabase pdContext, string strSeq) 565 { 566 if(pe == null) { Debug.Assert(false); return false; } 567 if(!pe.GetAutoTypeEnabled()) return false; 568 if(!AppPolicy.Try(AppPolicyId.AutoTypeWithoutContext)) return false; 569 570 bool bTopMost = ((fCurrent != null) ? fCurrent.TopMost : false); 571 if(bTopMost) fCurrent.TopMost = false; 572 573 try 574 { 575 if(!NativeMethods.LoseFocus(fCurrent, true)) { Debug.Assert(false); } 576 577 return PerformIntoCurrentWindow(pe, pdContext, strSeq); 578 } 579 finally 580 { 581 if(bTopMost) fCurrent.TopMost = true; 582 } 583 } 584 585 [Obsolete] PerformIntoCurrentWindow(PwEntry pe)586 public static bool PerformIntoCurrentWindow(PwEntry pe) 587 { 588 return PerformIntoCurrentWindow(pe, 589 Program.MainForm.DocumentManager.SafeFindContainerOf(pe), null); 590 } 591 PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext)592 public static bool PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext) 593 { 594 return PerformIntoCurrentWindow(pe, pdContext, null); 595 } 596 PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext, string strSeq)597 public static bool PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext, 598 string strSeq) 599 { 600 if(pe == null) { Debug.Assert(false); return false; } 601 if(!pe.GetAutoTypeEnabled()) return false; 602 if(!AppPolicy.Try(AppPolicyId.AutoTypeWithoutContext)) return false; 603 604 Thread.Sleep(TargetActivationDelay); 605 606 IntPtr hWnd; 607 string strWindow; 608 GetForegroundWindowInfo(out hWnd, out strWindow); 609 610 if(!NativeLib.IsUnix()) 611 { 612 if(strWindow == null) { Debug.Assert(false); return false; } 613 } 614 else strWindow = string.Empty; 615 616 if(strSeq == null) 617 { 618 SequenceQueriesEventArgs evQueries = GetSequencesForWindowBegin( 619 hWnd, strWindow); 620 621 List<string> lSeq = GetSequencesForWindow(pe, hWnd, strWindow, 622 pdContext, evQueries.EventID); 623 624 GetSequencesForWindowEnd(evQueries); 625 626 if(lSeq.Count == 0) strSeq = pe.GetAutoTypeSequence(); 627 else strSeq = lSeq[0]; 628 } 629 630 AutoTypeCtx ctx = new AutoTypeCtx(strSeq, pe, pdContext); 631 return AutoType.PerformInternal(ctx, strWindow); 632 } 633 634 // ValidateAutoTypeSequence is not required anymore, because 635 // SendInputEx now validates the sequence 636 /* private static string ValidateAutoTypeSequence(string strSequence) 637 { 638 Debug.Assert(strSequence != null); 639 640 string strSeq = strSequence; 641 strSeq = strSeq.Replace(@"{{}", string.Empty); 642 strSeq = strSeq.Replace(@"{}}", string.Empty); 643 644 int cBrackets = 0; 645 for(int c = 0; c < strSeq.Length; ++c) 646 { 647 if(strSeq[c] == '{') ++cBrackets; 648 else if(strSeq[c] == '}') --cBrackets; 649 650 if((cBrackets < 0) || (cBrackets > 1)) 651 return KPRes.AutoTypeSequenceInvalid; 652 } 653 if(cBrackets != 0) return KPRes.AutoTypeSequenceInvalid; 654 655 if(strSeq.IndexOf(@"{}") >= 0) return KPRes.AutoTypeSequenceInvalid; 656 657 try 658 { 659 Regex r = new Regex(@"\{[^\{\}]+\}", RegexOptions.CultureInvariant); 660 MatchCollection matches = r.Matches(strSeq); 661 662 foreach(Match m in matches) 663 { 664 string strValue = m.Value; 665 if(strValue.StartsWith(@"{s:", StrUtil.CaseIgnoreCmp)) 666 return (KPRes.AutoTypeUnknownPlaceholder + 667 MessageService.NewLine + strValue); 668 } 669 } 670 catch(Exception ex) { Debug.Assert(false); return ex.Message; } 671 672 return null; 673 } */ 674 675 private static readonly char[] g_vNormToHyphen = new char[] { 676 // Sync with UI option name 677 '\u2010', // Hyphen 678 '\u2011', // Non-breaking hyphen 679 '\u2012', // Figure dash 680 '\u2013', // En dash 681 '\u2014', // Em dash 682 '\u2015', // Horizontal bar 683 '\u2212' // Minus sign 684 }; NormalizeWindowText(string str)685 internal static string NormalizeWindowText(string str) 686 { 687 if(string.IsNullOrEmpty(str)) return string.Empty; 688 689 str = str.Trim(); 690 691 if(Program.Config.Integration.AutoTypeMatchNormDashes && 692 (str.IndexOfAny(g_vNormToHyphen) >= 0)) 693 { 694 for(int i = 0; i < g_vNormToHyphen.Length; ++i) 695 str = str.Replace(g_vNormToHyphen[i], '-'); 696 } 697 698 return str; 699 } 700 GetForegroundWindowInfo(out IntPtr hWnd, out string strWindow)701 private static void GetForegroundWindowInfo(out IntPtr hWnd, out string strWindow) 702 { 703 try 704 { 705 NativeMethods.GetForegroundWindowInfo(out hWnd, out strWindow, false); 706 } 707 catch(Exception) 708 { 709 Debug.Assert(false); 710 hWnd = IntPtr.Zero; 711 strWindow = null; 712 return; 713 } 714 715 strWindow = NormalizeWindowText(strWindow); 716 } 717 718 // Cf. PwEntry.GetAutoTypeEnabled GetEnabledText(PwEntry pe, out object oBlocker)719 internal static string GetEnabledText(PwEntry pe, out object oBlocker) 720 { 721 oBlocker = null; 722 if(pe == null) { Debug.Assert(false); return string.Empty; } 723 724 if(!pe.AutoType.Enabled) 725 { 726 oBlocker = pe; 727 return (KPRes.No + " (" + KLRes.EntryLower + ")"); 728 } 729 730 PwGroup pg = pe.ParentGroup; 731 while(pg != null) 732 { 733 if(pg.EnableAutoType.HasValue) 734 { 735 if(pg.EnableAutoType.Value) break; 736 737 oBlocker = pg; 738 return (KPRes.No + " (" + KLRes.GroupLower + " '" + pg.Name + "')"); 739 } 740 741 pg = pg.ParentGroup; 742 } 743 744 // Options like 'Expired entries can match' influence the global 745 // auto-type matching only, not commands like 'Perform Auto-Type' 746 747 return KPRes.Yes; 748 } 749 GetSequencesText(PwEntry pe)750 internal static string GetSequencesText(PwEntry pe) 751 { 752 if(pe == null) { Debug.Assert(false); return string.Empty; } 753 754 string strSeq = pe.GetAutoTypeSequence(); 755 Debug.Assert(strSeq.Length != 0); 756 string str = ((strSeq.Length != 0) ? strSeq : ("(" + KPRes.Empty + ")")); 757 758 int cAssoc = pe.AutoType.AssociationsCount; 759 if(cAssoc != 0) 760 { 761 Dictionary<string, bool> d = new Dictionary<string, bool>(); 762 d[strSeq] = true; 763 764 foreach(AutoTypeAssociation a in pe.AutoType.Associations) 765 { 766 string strAssocSeq = a.Sequence; 767 if(strAssocSeq.Length != 0) d[strAssocSeq] = true; 768 } 769 770 int c = d.Count; 771 str += ((c >= 2) ? (" " + KPRes.MoreAnd.Replace(@"{PARAM}", 772 (c - 1).ToString())) : string.Empty) + " (" + 773 cAssoc.ToString() + " " + KPRes.AssociationsLower + ")"; 774 } 775 776 return str; 777 } 778 } 779 } 780