diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/AutoTypeSearch/Throbber.gif b/AutoTypeSearch/Throbber.gif new file mode 100755 index 0000000..494d426 --- /dev/null +++ b/AutoTypeSearch/Throbber.gif Binary files differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/AutoTypeSearch/Throbber.gif b/AutoTypeSearch/Throbber.gif new file mode 100755 index 0000000..494d426 --- /dev/null +++ b/AutoTypeSearch/Throbber.gif Binary files differ diff --git a/AutoTypeSearch/app.config b/AutoTypeSearch/app.config new file mode 100755 index 0000000..1370758 --- /dev/null +++ b/AutoTypeSearch/app.config @@ -0,0 +1,60 @@ + + + + +
+ + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/AutoTypeSearch/Throbber.gif b/AutoTypeSearch/Throbber.gif new file mode 100755 index 0000000..494d426 --- /dev/null +++ b/AutoTypeSearch/Throbber.gif Binary files differ diff --git a/AutoTypeSearch/app.config b/AutoTypeSearch/app.config new file mode 100755 index 0000000..1370758 --- /dev/null +++ b/AutoTypeSearch/app.config @@ -0,0 +1,60 @@ + + + + +
+ + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + + \ No newline at end of file diff --git a/CreatePlgX.bat b/CreatePlgX.bat new file mode 100755 index 0000000..59b9aa0 --- /dev/null +++ b/CreatePlgX.bat @@ -0,0 +1,20 @@ +@echo off +cd %~dp0 + +echo Deleting existing PlgX folder +rmdir /s /q PlgX + +echo Creating PlgX folder +mkdir PlgX + +echo Copying files +xcopy "AutoTypeSearch" PlgX /s /e /exclude:PlgXExclude.txt + +echo Compiling PlgX +"../KeePass/KeePass.exe" /plgx-create "%~dp0PlgX" --plgx-prereq-kp:2.27 + +echo Releasing PlgX +move /y PlgX.plgx "Releases\Build Outputs\AutoTypeSearch.plgx" + +echo Cleaning up +rmdir /s /q PlgX diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/AutoTypeSearch/Throbber.gif b/AutoTypeSearch/Throbber.gif new file mode 100755 index 0000000..494d426 --- /dev/null +++ b/AutoTypeSearch/Throbber.gif Binary files differ diff --git a/AutoTypeSearch/app.config b/AutoTypeSearch/app.config new file mode 100755 index 0000000..1370758 --- /dev/null +++ b/AutoTypeSearch/app.config @@ -0,0 +1,60 @@ + + + + +
+ + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + + \ No newline at end of file diff --git a/CreatePlgX.bat b/CreatePlgX.bat new file mode 100755 index 0000000..59b9aa0 --- /dev/null +++ b/CreatePlgX.bat @@ -0,0 +1,20 @@ +@echo off +cd %~dp0 + +echo Deleting existing PlgX folder +rmdir /s /q PlgX + +echo Creating PlgX folder +mkdir PlgX + +echo Copying files +xcopy "AutoTypeSearch" PlgX /s /e /exclude:PlgXExclude.txt + +echo Compiling PlgX +"../KeePass/KeePass.exe" /plgx-create "%~dp0PlgX" --plgx-prereq-kp:2.27 + +echo Releasing PlgX +move /y PlgX.plgx "Releases\Build Outputs\AutoTypeSearch.plgx" + +echo Cleaning up +rmdir /s /q PlgX diff --git a/PlgXExclude.txt b/PlgXExclude.txt new file mode 100755 index 0000000..f626d58 --- /dev/null +++ b/PlgXExclude.txt @@ -0,0 +1,7 @@ +\bin\ +\obj\ +.user +.sln +.suo +.pdb +.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/AutoTypeSearch/Throbber.gif b/AutoTypeSearch/Throbber.gif new file mode 100755 index 0000000..494d426 --- /dev/null +++ b/AutoTypeSearch/Throbber.gif Binary files differ diff --git a/AutoTypeSearch/app.config b/AutoTypeSearch/app.config new file mode 100755 index 0000000..1370758 --- /dev/null +++ b/AutoTypeSearch/app.config @@ -0,0 +1,60 @@ + + + + +
+ + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + + \ No newline at end of file diff --git a/CreatePlgX.bat b/CreatePlgX.bat new file mode 100755 index 0000000..59b9aa0 --- /dev/null +++ b/CreatePlgX.bat @@ -0,0 +1,20 @@ +@echo off +cd %~dp0 + +echo Deleting existing PlgX folder +rmdir /s /q PlgX + +echo Creating PlgX folder +mkdir PlgX + +echo Copying files +xcopy "AutoTypeSearch" PlgX /s /e /exclude:PlgXExclude.txt + +echo Compiling PlgX +"../KeePass/KeePass.exe" /plgx-create "%~dp0PlgX" --plgx-prereq-kp:2.27 + +echo Releasing PlgX +move /y PlgX.plgx "Releases\Build Outputs\AutoTypeSearch.plgx" + +echo Cleaning up +rmdir /s /q PlgX diff --git a/PlgXExclude.txt b/PlgXExclude.txt new file mode 100755 index 0000000..f626d58 --- /dev/null +++ b/PlgXExclude.txt @@ -0,0 +1,7 @@ +\bin\ +\obj\ +.user +.sln +.suo +.pdb +.xml \ No newline at end of file diff --git a/Readme.txt b/Readme.txt new file mode 100755 index 0000000..96d2fc0 --- /dev/null +++ b/Readme.txt @@ -0,0 +1,103 @@ +AutoTypeSearch +============== +http://sourceforge.net/projects/autotypesearch + + +This is a plugin to KeePass to provide a quick searching capability as +an enhancement to the global auto-type system. If a global auto-type is requested, but no matching +entry for the active window is found, this plugin will show a quick as-you-type search window which +lets you to easily pick the entry to auto-type. + + +Installation +------------ +Place AutoTypeSearch.plgx in your KeePass Plugins folder. + + +Usage +----- +AutoTypeSearch is initially configured to automatically appear after an unsuccessful global +auto-type. However, this can be changed in the KeePass Options window (an AutoTypeShow tab has +been added). Here, a system-wide hot key can be configured to show the AutoTypeSearch window +immediately. It is also possible to show the window by running KeePass.exe passing "/e1:AutoTypeSearch" +as a command line parameter. + +Once the window is shown, usage is extremely simple. Just start typing, and AutoTypeSearch will +search your database for matching entries. By default, the Title, Url, Notes, Tags, and Custom Fields +will be searched, but this can be configured in the AutoTypeShow tab of the KeePass Options window. + +Protected fields (like Password) will not be searched. + +The arrow keys can be used to move the selection in the list of results, then press Enter to auto- +type the selected entry. Alternatively, press Shift+Enter to open the entry instead of auto-typing it. +(These actions can also be customised in the Options window.) Clicking and Shift-Clicking an entry will +also perform those actions. + + +Uninstallation +-------------- +Delete AutoTypeSearch.plgx from your KeePass Plugins folder. + + +Checking for updates +-------------------- +If you want to use the KeePass Check for Updates function to check for updates to this plugin +then it requires the SourceForgeUpdateChecker plugin to be installed too: +http://sourceforge.net/projects/kpsfupdatechecker + + +Bug Reporting, Questions, Comments, Feedback, Donations +------------------------------------------------------- +Please use the SourceForge project page: +Bugs can be reported using the issue tracker, for anything else, a discussion forum is available. + + +Changelog +--------- +v0.1 + Initial release + +v0.2 + Added information banner when search is shown as a result of an unsuccessful global auto-type + Compatibility with Linux/Mono + +v0.3 + Added search result prioritisation for entries where the match is found at the start of the field + +v0.4 + Added support for multiple databases. All currently open, unlocked, databases will be searched + +v0.5 + Added support for KeePass 2.29 high resolution custom icons + +v0.6 + Where title does not uniquely identify the results shown, now also shows the group name as context + +v0.7 + Added support for the "Open entry URL" action. Use the Options window to choose this, if required. + +v0.8 + Added support for the "Copy password" action. Use the Options window to choose this, if required. + +v0.9 + Added workaround for mono bug under Linux that could cause an ArgumentOutOfRange crash when + searching if only a single result is initially returned. + +v0.91 + Fixed bug where up or down keys would cause an exception if there are no results to scroll through + +v0.10 + Compatibility with KeePass 2.41 (No longer compatible with previous versions) + +v0.11 + Diacritic (accent) insensitive searching + +v0.12 + Removed ugly white top border under Windows 10 + +v1.0 + Compatibility with KeePass 2.42. For versions of KeePass prior to 2.42, use an 0.X version. + +Attributions +------------ +Throbber image by FlipDarius http://www.mediawiki.org/wiki/File:Loading.gif \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54be39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Releases/* +!Releases/PackageRelease.bat diff --git a/AutoTypeSearch/.gitignore b/AutoTypeSearch/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/AutoTypeSearch/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/AutoTypeSearch/Actions.cs b/AutoTypeSearch/Actions.cs new file mode 100755 index 0000000..096c515 --- /dev/null +++ b/AutoTypeSearch/Actions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace AutoTypeSearch +{ + internal enum Actions + { + PerformAutoType, + EditEntry, + ShowEntry, + OpenEntryUrl, + CopyPassword + } +} diff --git a/AutoTypeSearch/AutoTypeSearch.csproj b/AutoTypeSearch/AutoTypeSearch.csproj new file mode 100755 index 0000000..7be4bdd --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635} + Library + Properties + AutoTypeSearch + AutoTypeSearch + v4.6.1 + 512 + + + + true + full + false + ..\..\KeePass-Source\Build\KeePass\Debug\Plugins\AutoTypeSearch\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + false + bin\Release\ + TRACE + prompt + 4 + false + + + + + + {10938016-DEE2-4A25-9A5A-8FD3444379CA} + KeePass + False + + + + + + + ..\..\KeePass\KeePass.exe + False + + + + + + + + + + + + + + + + UserControl + + + Options.cs + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + Form + + + SearchWindow.cs + + + + + Options.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SearchWindow.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + IF $(ConfigurationName) == Release "$(ProjectDir)..\CreatePlgX.bat" + + + \ No newline at end of file diff --git a/AutoTypeSearch/AutoTypeSearch.sln b/AutoTypeSearch/AutoTypeSearch.sln new file mode 100755 index 0000000..5812d0e --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTypeSearch", "AutoTypeSearch.csproj", "{CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePass", "..\..\KeePass-Source\KeePass\KeePass.csproj", "{10938016-DEE2-4A25-9A5A-8FD3444379CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93BF1946-D769-4387-B47C-6269FBCE2303}" + ProjectSection(SolutionItems) = preProject + ..\Releases\PackageRelease.bat = ..\Releases\PackageRelease.bat + ..\Readme.txt = ..\Readme.txt + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1AD3A1-EEEB-47C5-BB7E-43B59BDC9635}.Release|Any CPU.Build.0 = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10938016-DEE2-4A25-9A5A-8FD3444379CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AutoTypeSearch/AutoTypeSearchExt.cs b/AutoTypeSearch/AutoTypeSearchExt.cs new file mode 100755 index 0000000..850bcd6 --- /dev/null +++ b/AutoTypeSearch/AutoTypeSearchExt.cs @@ -0,0 +1,195 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass; +using KeePass.Forms; +using KeePass.Plugins; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Security; + +namespace AutoTypeSearch +{ +// ReSharper disable once ClassNeverInstantiated.Global - Plugin instantiated by KeePass + public sealed class AutoTypeSearchExt : Plugin + { + private const string IpcEventName = "AutoTypeSearch"; + private const int UnixAutoTypeWaitTime = 500; // Milliseconds + internal const string TagsVirtualFieldName = "***TAGS***"; + + private IPluginHost mHost; + private bool mAutoTypeSuccessful; + private string mLastAutoTypeWindowTitle; + + public override string UpdateUrl + { + get { return "sourceforge-version://AutoTypeSearch/autotypesearch?-v(%5B%5Cd.%5D%2B)%5C.zip"; } + } + + public override bool Initialize(IPluginHost host) + { + mHost = host; + + IpcUtilEx.IpcEvent += OnIpcEvent; + GlobalWindowManager.WindowAdded += OnWindowAdded; + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed; + } + AutoType.SequenceQueriesEnd += OnAutoTypeSequenceQueriesEnd; + + Options.LoadSettings(host); + + return true; + } + + #region Unsuccessful AutoType Detection + private void OnAutoTypeSequenceQueriesEnd(object sender, SequenceQueriesEventArgs e) + { + // An auto-type has completed. Was it successful? Watch for an auto-type event, and for the UI thread unblocking. If the UI thread unblocks before the auto-type event, it wasn't successful. + // (hacky, yes, but no other means possible to detect failed auto-types at the time of writing) + + if (Settings.Default.ShowOnFailedAutoType) + { + mAutoTypeSuccessful = false; + mLastAutoTypeWindowTitle = e.TargetWindowTitle; + AutoType.FilterCompilePre += OnAutoType; + + if (KeePassLib.Native.NativeLib.IsUnix()) + { + // If Unix, can't rely on waiting for UI thread unblocking as the XDoTool mechanism calls DoEvents (in NativeMethods.TryXDoTool) before anything else. + // Instead, just wait half a second and hope for the best. + var timer = new Timer { Interval = UnixAutoTypeWaitTime }; + timer.Tick += delegate + { + timer.Stop(); + timer.Dispose(); + OnAutoTypeEnd(); + }; + timer.Start(); + } + else + { + mHost.MainWindow.BeginInvoke((Action)OnAutoTypeEnd); + } + } + } + + private void OnAutoType(object sender, AutoTypeEventArgs autoTypeEventArgs) + { + // Detach event, we are only interested in a single invocation. + AutoType.FilterCompilePre -= OnAutoType; + + mAutoTypeSuccessful = true; + } + + private void OnAutoTypeEnd() + { + // Detach event, the auto-type failed, it won't be received now. + AutoType.FilterCompilePre -= OnAutoType; + + if (!mAutoTypeSuccessful) + { + ShowSearch(String.Format(Resources.AutoTypeFailedMessage, mLastAutoTypeWindowTitle)); + } + } + #endregion + + #region Options + private void OnWindowAdded(object sender, GwmWindowEventArgs e) + { + var optionsForm = e.Form as OptionsForm; + if (optionsForm != null) + { + Options.AddToWindow(optionsForm); + return; + } + + if (Settings.Default.ShowOnFailedAutoType) + { + var autoTypeCtxForm = e.Form as AutoTypeCtxForm; + if (autoTypeCtxForm != null) + { + mAutoTypeSuccessful = true; // Don't show the search if the picker box is shown + autoTypeCtxForm.Closed += OnAutoTypeCtxFormClosed; + } + } + } + + private void OnAutoTypeCtxFormClosed(object sender, EventArgs e) + { + var autoTypeCtxForm = (AutoTypeCtxForm)sender; + autoTypeCtxForm.Closed -= OnAutoTypeCtxFormClosed; + + if (autoTypeCtxForm.DialogResult == DialogResult.Cancel) + { + ShowSearch(); + } + } + #endregion + + public override void Terminate() + { + IpcUtilEx.IpcEvent -= OnIpcEvent; + GlobalWindowManager.WindowAdded -= OnWindowAdded; + + if (!KeePassLib.Native.NativeLib.IsUnix()) + { + HotKeyManager.HotKeyPressed -= HotKeyManager_HotKeyPressed; + Options.UnregisterHotKey(); + } + + Options.SaveSettings(mHost); + + base.Terminate(); + } + + #region Search Initiation + private void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) + { + /* + var testGroup = mHost.Database.RootGroup.FindCreateGroup("Test", true); + for (int i = 0; i < 10000; i++) + { + var pwEntry = new PwEntry(true, true); + pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, "Title " + i)); + pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "User " + i)); + pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "http://website/" + i)); + pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "Notes " + i + "\nLine 2\n\nLine 3\nLine 4\nLine 5\n Line 6\n Line 7\nLine 8\nLine 9\nLine 10")); + testGroup.AddEntry(pwEntry, true); + }*/ + + ShowSearch(); + } + + private void OnIpcEvent(object sender, IpcEventArgs ipcEventArgs) + { + if (Settings.Default.ShowOnIPC && ipcEventArgs.Name.Equals(IpcEventName, StringComparison.InvariantCultureIgnoreCase)) + { + mHost.MainWindow.BeginInvoke(new Action(ShowSearch)); + } + } + + private void ShowSearch() + { + ShowSearch(null); + } + + private void ShowSearch(string infoText) + { + // Unlock, if required + mHost.MainWindow.ProcessAppMessage((IntPtr)Program.AppMessage.Unlock, IntPtr.Zero); + + + if (mHost.MainWindow.IsAtLeastOneFileOpen()) + { + var searchWindow = new SearchWindow(mHost.MainWindow, infoText); + searchWindow.Show(); + searchWindow.Activate(); + } + } + #endregion + } +} diff --git a/AutoTypeSearch/HotKeyManager.cs b/AutoTypeSearch/HotKeyManager.cs new file mode 100755 index 0000000..b33f84b --- /dev/null +++ b/AutoTypeSearch/HotKeyManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + // This class taken from: http://stackoverflow.com/questions/3568513/how-to-create-keyboard-shortcut-in-windows-that-call-function-in-my-app/3569097#3569097 + // And tweaked with answers in: http://stackoverflow.com/questions/15434505/key-capture-using-global-hotkey-in-c-sharp + // And logic from KeePass HotKeyManager + internal static class HotKeyManager + { + public static event EventHandler HotKeyPressed; + + public static int RegisterHotKey(Keys keys) + { + int id = System.Threading.Interlocked.Increment(ref _id); + + KeyModifiers modifiers = 0; + if ((keys & Keys.Shift) != Keys.None) modifiers |= KeyModifiers.Shift; + if ((keys & Keys.Alt) != Keys.None) modifiers |= KeyModifiers.Alt; + if ((keys & Keys.Control) != Keys.None) modifiers |= KeyModifiers.Control; + + RegisterHotKey(_wnd.Handle, id, (uint)modifiers, (uint)(keys & Keys.KeyCode)); + return id; + } + + public static bool UnregisterHotKey(int id) + { + return UnregisterHotKey(_wnd.Handle, id); + } + + private static void OnHotKeyPressed(HotKeyEventArgs e) + { + if (HotKeyManager.HotKeyPressed != null) + { + HotKeyManager.HotKeyPressed(null, e); + } + } + + private static MessageWindow _wnd = new MessageWindow(); + + private class MessageWindow : NativeWindow, IDisposable + { + public MessageWindow() + { + CreateHandle(new CreateParams()); + } + + public void Dispose() + { + DestroyHandle(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_HOTKEY) + { + HotKeyEventArgs e = new HotKeyEventArgs(m.LParam); + HotKeyManager.OnHotKeyPressed(e); + } + + base.WndProc(ref m); + } + + private const int WM_HOTKEY = 0x312; + } + + [DllImport("user32")] + private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32")] + private static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + private static int _id = 0; + } + + + public class HotKeyEventArgs : EventArgs + { + public readonly Keys Key; + public readonly KeyModifiers Modifiers; + + public HotKeyEventArgs(Keys key, KeyModifiers modifiers) + { + this.Key = key; + this.Modifiers = modifiers; + } + + public HotKeyEventArgs(IntPtr hotKeyParam) + { + uint param = (uint)hotKeyParam.ToInt64(); + Key = (Keys)((param & 0xffff0000) >> 16); + Modifiers = (KeyModifiers)(param & 0x0000ffff); + } + } + + [Flags] + public enum KeyModifiers + { + Alt = 1, + Control = 2, + Shift = 4, + Windows = 8, + NoRepeat = 0x4000 + } +} \ No newline at end of file diff --git a/AutoTypeSearch/Info.png b/AutoTypeSearch/Info.png new file mode 100755 index 0000000..c1a5608 --- /dev/null +++ b/AutoTypeSearch/Info.png Binary files differ diff --git a/AutoTypeSearch/NativeMethods.cs b/AutoTypeSearch/NativeMethods.cs new file mode 100755 index 0000000..0037441 --- /dev/null +++ b/AutoTypeSearch/NativeMethods.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using KeePassLib.Native; +using Microsoft.Win32; + +namespace AutoTypeSearch +{ + internal static class NativeMethods + { + private const int EM_SETMARGINS = 0x00D3; + private const int EC_RIGHTMARGIN = 0x2; + + private const int WM_NCLBUTTONDOWN = 0xA1; + private const int HTCAPTION = 0x2; + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + private const int SWP_NOSIZE = 0x0001; + private const int SWP_NOMOVE = 0x0002; + private const int SWP_NOZORDER = 0x0004; + private const int SWP_FRAMECHANGED = 0x0020; + [DllImport("user32.dll", SetLastError=true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + + private const int WM_NCCALCSIZE = 0x83; + + private struct RECT + { + public int Left, Top, Right, Bottom; + } + private struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndinsertafter; + public int x, y, cx, cy; + public int flags; + } + + struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + + public static void SetTextBoxRightMargin(TextBox control, int rightMargin) + { + SendMessage(control.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin << 16); + } + + public static void StartFormDrag(Form form) + { + Debug.Assert(Control.MouseButtons == MouseButtons.Left); + ReleaseCapture(); + SendMessage(form.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + public static void RefreshWindowFrame(IntPtr hWnd) + { + NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + public static void RemoveWindowFrameTopBorder(ref Message m, int borderHeight) + { + if (m.Msg == WM_NCCALCSIZE) + { + var csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS)); + csp.rgrc[0].Top -= borderHeight; + Marshal.StructureToPtr(csp, m.LParam, false); + } + } + + public static bool IsWindows10() + { + return NativeLib.GetPlatformID() == PlatformID.Win32NT && + // Can't just use OS Version because Windows 10 lies if you don't have specific support declared in the manifest. + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", -1) == 10; + } + } +} diff --git a/AutoTypeSearch/Options.Designer.cs b/AutoTypeSearch/Options.Designer.cs new file mode 100755 index 0000000..4886b6d --- /dev/null +++ b/AutoTypeSearch/Options.Designer.cs @@ -0,0 +1,324 @@ +using KeePass.UI; + +namespace AutoTypeSearch +{ + partial class Options + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.GroupBox searchOptionsGroup; + System.Windows.Forms.GroupBox searchInGroup; + System.Windows.Forms.GroupBox actionsGroup; + System.Windows.Forms.Label alternativeActionLabel; + System.Windows.Forms.Label defaultActionLabel; + this.mResolveReferences = new System.Windows.Forms.CheckBox(); + this.mExcludeExpired = new System.Windows.Forms.CheckBox(); + this.mCaseSensitive = new System.Windows.Forms.CheckBox(); + this.mSearchInTags = new System.Windows.Forms.CheckBox(); + this.mSearchInOtherFields = new System.Windows.Forms.CheckBox(); + this.mSearchInNotes = new System.Windows.Forms.CheckBox(); + this.mSearchInUrl = new System.Windows.Forms.CheckBox(); + this.mSearchInUserName = new System.Windows.Forms.CheckBox(); + this.mSearchInTitle = new System.Windows.Forms.CheckBox(); + this.mAlternativeAction = new System.Windows.Forms.ComboBox(); + this.mDefaultAction = new System.Windows.Forms.ComboBox(); + this.mShowHotKeyControl = new KeePass.UI.HotKeyControlEx(); + this.mShowSearchGroup = new System.Windows.Forms.GroupBox(); + this.mShowOnHotKey = new System.Windows.Forms.CheckBox(); + this.mShowOnIPC = new System.Windows.Forms.CheckBox(); + this.mShowOnFailedSearch = new System.Windows.Forms.CheckBox(); + searchOptionsGroup = new System.Windows.Forms.GroupBox(); + searchInGroup = new System.Windows.Forms.GroupBox(); + actionsGroup = new System.Windows.Forms.GroupBox(); + alternativeActionLabel = new System.Windows.Forms.Label(); + defaultActionLabel = new System.Windows.Forms.Label(); + searchOptionsGroup.SuspendLayout(); + searchInGroup.SuspendLayout(); + actionsGroup.SuspendLayout(); + this.mShowSearchGroup.SuspendLayout(); + this.SuspendLayout(); + // + // searchOptionsGroup + // + searchOptionsGroup.Controls.Add(this.mResolveReferences); + searchOptionsGroup.Controls.Add(this.mExcludeExpired); + searchOptionsGroup.Controls.Add(this.mCaseSensitive); + searchOptionsGroup.Location = new System.Drawing.Point(6, 189); + searchOptionsGroup.Name = "searchOptionsGroup"; + searchOptionsGroup.Size = new System.Drawing.Size(540, 45); + searchOptionsGroup.TabIndex = 2; + searchOptionsGroup.TabStop = false; + searchOptionsGroup.Text = "Search options"; + // + // mResolveReferences + // + this.mResolveReferences.AutoSize = true; + this.mResolveReferences.Location = new System.Drawing.Point(251, 20); + this.mResolveReferences.Name = "mResolveReferences"; + this.mResolveReferences.Size = new System.Drawing.Size(170, 17); + this.mResolveReferences.TabIndex = 2; + this.mResolveReferences.Text = "Resolve fiel&d references (slow)"; + this.mResolveReferences.UseVisualStyleBackColor = true; + // + // mExcludeExpired + // + this.mExcludeExpired.AutoSize = true; + this.mExcludeExpired.Location = new System.Drawing.Point(108, 20); + this.mExcludeExpired.Name = "mExcludeExpired"; + this.mExcludeExpired.Size = new System.Drawing.Size(135, 17); + this.mExcludeExpired.TabIndex = 1; + this.mExcludeExpired.Text = "Exclude &expired entries"; + this.mExcludeExpired.UseVisualStyleBackColor = true; + // + // mCaseSensitive + // + this.mCaseSensitive.AutoSize = true; + this.mCaseSensitive.Location = new System.Drawing.Point(10, 20); + this.mCaseSensitive.Name = "mCaseSensitive"; + this.mCaseSensitive.Size = new System.Drawing.Size(94, 17); + this.mCaseSensitive.TabIndex = 0; + this.mCaseSensitive.Text = "Case-sensiti&ve"; + this.mCaseSensitive.UseVisualStyleBackColor = true; + // + // searchInGroup + // + searchInGroup.Controls.Add(this.mSearchInTags); + searchInGroup.Controls.Add(this.mSearchInOtherFields); + searchInGroup.Controls.Add(this.mSearchInNotes); + searchInGroup.Controls.Add(this.mSearchInUrl); + searchInGroup.Controls.Add(this.mSearchInUserName); + searchInGroup.Controls.Add(this.mSearchInTitle); + searchInGroup.Location = new System.Drawing.Point(6, 136); + searchInGroup.Name = "searchInGroup"; + searchInGroup.Size = new System.Drawing.Size(540, 47); + searchInGroup.TabIndex = 1; + searchInGroup.TabStop = false; + searchInGroup.Text = "Search in"; + // + // mSearchInTags + // + this.mSearchInTags.AutoSize = true; + this.mSearchInTags.Location = new System.Drawing.Point(258, 19); + this.mSearchInTags.Name = "mSearchInTags"; + this.mSearchInTags.Size = new System.Drawing.Size(50, 17); + this.mSearchInTags.TabIndex = 4; + this.mSearchInTags.Text = "Ta&gs"; + this.mSearchInTags.UseVisualStyleBackColor = true; + // + // mSearchInOtherFields + // + this.mSearchInOtherFields.AutoSize = true; + this.mSearchInOtherFields.Location = new System.Drawing.Point(314, 19); + this.mSearchInOtherFields.Name = "mSearchInOtherFields"; + this.mSearchInOtherFields.Size = new System.Drawing.Size(139, 17); + this.mSearchInOtherFields.TabIndex = 5; + this.mSearchInOtherFields.Text = "&Other unprotected fields"; + this.mSearchInOtherFields.UseVisualStyleBackColor = true; + // + // mSearchInNotes + // + this.mSearchInNotes.AutoSize = true; + this.mSearchInNotes.Location = new System.Drawing.Point(198, 19); + this.mSearchInNotes.Name = "mSearchInNotes"; + this.mSearchInNotes.Size = new System.Drawing.Size(54, 17); + this.mSearchInNotes.TabIndex = 3; + this.mSearchInNotes.Text = "Note&s"; + this.mSearchInNotes.UseVisualStyleBackColor = true; + // + // mSearchInUrl + // + this.mSearchInUrl.AutoSize = true; + this.mSearchInUrl.Location = new System.Drawing.Point(144, 19); + this.mSearchInUrl.Name = "mSearchInUrl"; + this.mSearchInUrl.Size = new System.Drawing.Size(48, 17); + this.mSearchInUrl.TabIndex = 2; + this.mSearchInUrl.Text = "&URL"; + this.mSearchInUrl.UseVisualStyleBackColor = true; + // + // mSearchInUserName + // + this.mSearchInUserName.AutoSize = true; + this.mSearchInUserName.Location = new System.Drawing.Point(61, 19); + this.mSearchInUserName.Name = "mSearchInUserName"; + this.mSearchInUserName.Size = new System.Drawing.Size(77, 17); + this.mSearchInUserName.TabIndex = 1; + this.mSearchInUserName.Text = "User &name"; + this.mSearchInUserName.UseVisualStyleBackColor = true; + // + // mSearchInTitle + // + this.mSearchInTitle.AutoSize = true; + this.mSearchInTitle.Location = new System.Drawing.Point(9, 19); + this.mSearchInTitle.Name = "mSearchInTitle"; + this.mSearchInTitle.Size = new System.Drawing.Size(46, 17); + this.mSearchInTitle.TabIndex = 0; + this.mSearchInTitle.Text = "&Title"; + this.mSearchInTitle.UseVisualStyleBackColor = true; + // + // actionsGroup + // + actionsGroup.Controls.Add(this.mAlternativeAction); + actionsGroup.Controls.Add(this.mDefaultAction); + actionsGroup.Controls.Add(alternativeActionLabel); + actionsGroup.Controls.Add(defaultActionLabel); + actionsGroup.Location = new System.Drawing.Point(6, 241); + actionsGroup.Name = "actionsGroup"; + actionsGroup.Size = new System.Drawing.Size(540, 67); + actionsGroup.TabIndex = 3; + actionsGroup.TabStop = false; + actionsGroup.Text = "Actions"; + // + // mAlternativeAction + // + this.mAlternativeAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mAlternativeAction.Location = new System.Drawing.Point(288, 37); + this.mAlternativeAction.Name = "mAlternativeAction"; + this.mAlternativeAction.Size = new System.Drawing.Size(240, 21); + this.mAlternativeAction.TabIndex = 3; + // + // mDefaultAction + // + this.mDefaultAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDefaultAction.Location = new System.Drawing.Point(11, 37); + this.mDefaultAction.Name = "mDefaultAction"; + this.mDefaultAction.Size = new System.Drawing.Size(240, 21); + this.mDefaultAction.TabIndex = 1; + // + // alternativeActionLabel + // + alternativeActionLabel.AutoSize = true; + alternativeActionLabel.Location = new System.Drawing.Point(285, 20); + alternativeActionLabel.Name = "alternativeActionLabel"; + alternativeActionLabel.Size = new System.Drawing.Size(159, 13); + alternativeActionLabel.TabIndex = 2; + alternativeActionLabel.Text = "A<ernative action (Shift + Enter):"; + // + // defaultActionLabel + // + defaultActionLabel.AutoSize = true; + defaultActionLabel.Location = new System.Drawing.Point(8, 20); + defaultActionLabel.Name = "defaultActionLabel"; + defaultActionLabel.Size = new System.Drawing.Size(110, 13); + defaultActionLabel.TabIndex = 0; + defaultActionLabel.Text = "De&fault action (Enter):"; + // + // mShowHotKeyControl + // + this.mShowHotKeyControl.Location = new System.Drawing.Point(30, 65); + this.mShowHotKeyControl.Name = "mShowHotKeyControl"; + this.mShowHotKeyControl.Size = new System.Drawing.Size(123, 20); + this.mShowHotKeyControl.TabIndex = 2; + // + // mShowSearchGroup + // + this.mShowSearchGroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mShowSearchGroup.Controls.Add(this.mShowOnHotKey); + this.mShowSearchGroup.Controls.Add(this.mShowHotKeyControl); + this.mShowSearchGroup.Controls.Add(this.mShowOnIPC); + this.mShowSearchGroup.Controls.Add(this.mShowOnFailedSearch); + this.mShowSearchGroup.Location = new System.Drawing.Point(6, 12); + this.mShowSearchGroup.Name = "mShowSearchGroup"; + this.mShowSearchGroup.Size = new System.Drawing.Size(540, 118); + this.mShowSearchGroup.TabIndex = 0; + this.mShowSearchGroup.TabStop = false; + this.mShowSearchGroup.Text = "Show search window"; + // + // mShowOnHotKey + // + this.mShowOnHotKey.AutoSize = true; + this.mShowOnHotKey.Location = new System.Drawing.Point(10, 44); + this.mShowOnHotKey.Name = "mShowOnHotKey"; + this.mShowOnHotKey.Size = new System.Drawing.Size(233, 17); + this.mShowOnHotKey.TabIndex = 1; + this.mShowOnHotKey.Text = "Show when system-wide &hot key is pressed:"; + this.mShowOnHotKey.UseVisualStyleBackColor = true; + this.mShowOnHotKey.CheckedChanged += new System.EventHandler(this.mShowOnHotKey_CheckedChanged); + // + // mShowOnIPC + // + this.mShowOnIPC.AutoSize = true; + this.mShowOnIPC.Location = new System.Drawing.Point(10, 93); + this.mShowOnIPC.Name = "mShowOnIPC"; + this.mShowOnIPC.Size = new System.Drawing.Size(386, 17); + this.mShowOnIPC.TabIndex = 3; + this.mShowOnIPC.Text = "Show when \"/e1:AutoTypeSearch\" is passed as a ¶meter to KeePass.exe"; + this.mShowOnIPC.UseVisualStyleBackColor = true; + // + // mShowOnFailedSearch + // + this.mShowOnFailedSearch.AutoSize = true; + this.mShowOnFailedSearch.Location = new System.Drawing.Point(10, 21); + this.mShowOnFailedSearch.Name = "mShowOnFailedSearch"; + this.mShowOnFailedSearch.Size = new System.Drawing.Size(275, 17); + this.mShowOnFailedSearch.TabIndex = 0; + this.mShowOnFailedSearch.Text = "Show &automatically if global auto-type finds no match"; + this.mShowOnFailedSearch.UseVisualStyleBackColor = true; + // + // Options + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(actionsGroup); + this.Controls.Add(searchInGroup); + this.Controls.Add(searchOptionsGroup); + this.Controls.Add(this.mShowSearchGroup); + this.Name = "Options"; + this.Size = new System.Drawing.Size(551, 311); + searchOptionsGroup.ResumeLayout(false); + searchOptionsGroup.PerformLayout(); + searchInGroup.ResumeLayout(false); + searchInGroup.PerformLayout(); + actionsGroup.ResumeLayout(false); + actionsGroup.PerformLayout(); + this.mShowSearchGroup.ResumeLayout(false); + this.mShowSearchGroup.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private KeePass.UI.HotKeyControlEx mShowHotKeyControl; + private System.Windows.Forms.CheckBox mShowOnHotKey; + private System.Windows.Forms.CheckBox mShowOnIPC; + private System.Windows.Forms.CheckBox mShowOnFailedSearch; + private System.Windows.Forms.CheckBox mCaseSensitive; + private System.Windows.Forms.CheckBox mSearchInTags; + private System.Windows.Forms.CheckBox mSearchInOtherFields; + private System.Windows.Forms.CheckBox mSearchInNotes; + private System.Windows.Forms.CheckBox mSearchInUrl; + private System.Windows.Forms.CheckBox mSearchInUserName; + private System.Windows.Forms.CheckBox mSearchInTitle; + private System.Windows.Forms.CheckBox mResolveReferences; + private System.Windows.Forms.CheckBox mExcludeExpired; + private System.Windows.Forms.ComboBox mAlternativeAction; + private System.Windows.Forms.ComboBox mDefaultAction; + private System.Windows.Forms.GroupBox mShowSearchGroup; + + } +} diff --git a/AutoTypeSearch/Options.cs b/AutoTypeSearch/Options.cs new file mode 100755 index 0000000..b99561c --- /dev/null +++ b/AutoTypeSearch/Options.cs @@ -0,0 +1,191 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Plugins; +using KeePassLib; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + internal partial class Options : UserControl + { + private const string OptionsConfigRoot = "AutoTypeSearchExt."; + + private static int sRegisteredHotkeyId; + + // ReSharper disable once MemberCanBePrivate.Global - Public for forms designer + public Options() + { + InitializeComponent(); + + // Must mach order and values of Actions enum + var actions = new object[] { Resources.PerformAutoType, Resources.EditEntry, Resources.ShowEntry, Resources.OpenEntryUrl, Resources.CopyPassword }; + mDefaultAction.Items.AddRange(actions); + mAlternativeAction.Items.AddRange(actions); + + // Read options + mShowOnFailedSearch.Checked = Settings.Default.ShowOnFailedAutoType; + + if (NativeLib.IsUnix()) + { + mShowOnHotKey.Enabled = false; + mShowOnHotKey.Checked = false; + + mShowHotKeyControl.Clear(); + } + else + { + mShowOnHotKey.Checked = Settings.Default.ShowOnHotKey; + ShowHotKey = Settings.Default.ShowHotKey; + } + mShowOnHotKey_CheckedChanged(null, EventArgs.Empty); + + mShowOnIPC.Checked = Settings.Default.ShowOnIPC; + mSearchInTitle.Checked = Settings.Default.SearchTitle; + mSearchInUserName.Checked = Settings.Default.SearchUserName; + mSearchInUrl.Checked = Settings.Default.SearchUrl; + mSearchInNotes.Checked = Settings.Default.SearchNotes; + mSearchInTags.Checked = Settings.Default.SearchTags; + mSearchInOtherFields.Checked = Settings.Default.SearchCustomFields; + + mCaseSensitive.Checked = Settings.Default.CaseSensitive; + mExcludeExpired.Checked = Settings.Default.ExcludeExpired; + mResolveReferences.Checked = Settings.Default.ResolveReferences; + + mDefaultAction.SelectedIndex = (int)Settings.Default.DefaultAction; + mAlternativeAction.SelectedIndex = (int)Settings.Default.AlternativeAction; + } + + private Keys ShowHotKey + { + get { return mShowHotKeyControl.HotKey; } + set { mShowHotKeyControl.HotKey = value; } + } + + private void mShowOnHotKey_CheckedChanged(object sender, EventArgs e) + { + mShowHotKeyControl.Enabled = mShowOnHotKey.Checked; + } + + private void ApplySettings() + { + // Apply settings + Settings.Default.ShowOnFailedAutoType = mShowOnFailedSearch.Checked; + Settings.Default.ShowOnHotKey = mShowOnHotKey.Checked; + Settings.Default.ShowOnIPC = mShowOnIPC.Checked; + Settings.Default.SearchTitle = mSearchInTitle.Checked; + Settings.Default.SearchUserName = mSearchInUserName.Checked; + Settings.Default.SearchUrl = mSearchInUrl.Checked; + Settings.Default.SearchNotes = mSearchInNotes.Checked; + Settings.Default.SearchTags = mSearchInTags.Checked; + Settings.Default.SearchCustomFields = mSearchInOtherFields.Checked; + Settings.Default.CaseSensitive = mCaseSensitive.Checked; + Settings.Default.ExcludeExpired = mExcludeExpired.Checked; + Settings.Default.ResolveReferences = mResolveReferences.Checked; + Settings.Default.DefaultAction = (Actions)mDefaultAction.SelectedIndex; + Settings.Default.AlternativeAction = (Actions)mAlternativeAction.SelectedIndex; + Settings.Default.ShowHotKey = ShowHotKey; + + ApplyHotKey(); + } + + #region Settings persistence + public static void SaveSettings(IPluginHost host) + { + if (host != null) + { + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + if (property.IsDirty) + { + var value = property.SerializedValue as String; + if (value != null) + { + host.CustomConfig.SetString(OptionsConfigRoot + property.Name, value); + } + else + { + Debug.Fail("Non-string serialized settings property"); + } + } + } + } + } + + public static void LoadSettings(IPluginHost host) + { + if (host != null) + { + // ReSharper disable once UnusedVariable + var ignored = Settings.Default.ShowOnFailedAutoType; //Access any property just to make it load settings. + + foreach (SettingsPropertyValue property in Settings.Default.PropertyValues) + { + var value = host.CustomConfig.GetString(OptionsConfigRoot + property.Name); + if (value != null) + { + property.SerializedValue = value; + property.Deserialized = false; + property.IsDirty = false; + } + } + + ApplyHotKey(); + } + } + #endregion + + #region Hotkey + private static void ApplyHotKey() + { + UnregisterHotKey(); + + if (Settings.Default.ShowOnHotKey && Settings.Default.ShowHotKey != Keys.None) + { + sRegisteredHotkeyId = HotKeyManager.RegisterHotKey(Settings.Default.ShowHotKey); + } + } + + public static void UnregisterHotKey() + { + if (sRegisteredHotkeyId != 0) + { + var result = HotKeyManager.UnregisterHotKey(sRegisteredHotkeyId); + Debug.Assert(result); + sRegisteredHotkeyId = 0; + } + } + #endregion + + public static void AddToWindow(OptionsForm optionsForm) + { + var tabControl = optionsForm.Controls.Find("m_tabMain", false).FirstOrDefault() as TabControl; + var okButton = optionsForm.Controls.Find("m_btnOK", false).FirstOrDefault() as Button; + + if (tabControl == null || okButton == null) + { + Debug.Fail("Could not integrate with options form"); + } + + var tabPage = new TabPage(Resources.AutoTypeSearch) + { + UseVisualStyleBackColor = true, + AutoScroll = true, + ImageIndex = (int)PwIcon.EMailSearch + }; + var options = new Options { Dock = DockStyle.Fill }; + tabPage.Controls.Add(options); + + tabControl.TabPages.Add(tabPage); + + okButton.Click += delegate + { + options.ApplySettings(); + }; + } + } +} diff --git a/AutoTypeSearch/Options.resx b/AutoTypeSearch/Options.resx new file mode 100755 index 0000000..4601c27 --- /dev/null +++ b/AutoTypeSearch/Options.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/AssemblyInfo.cs b/AutoTypeSearch/Properties/AssemblyInfo.cs new file mode 100755 index 0000000..4a8b0ac --- /dev/null +++ b/AutoTypeSearch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoTypeSearch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Alex Vallat")] +[assembly: AssemblyProduct("KeePass Plugin")] +[assembly: AssemblyCopyright("Copyright © 2017 Alex Vallat")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c4effc53-d77b-45e0-9d11-a0b9661ae822")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.42.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AutoTypeSearch/Properties/Resources.Designer.cs b/AutoTypeSearch/Properties/Resources.Designer.cs new file mode 100755 index 0000000..4a4fbaf --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.Designer.cs @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoTypeSearch.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Global auto-type found no match for window: "{0}". + /// + internal static string AutoTypeFailedMessage { + get { + return ResourceManager.GetString("AutoTypeFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AutoTypeSearch. + /// + internal static string AutoTypeSearch { + get { + return ResourceManager.GetString("AutoTypeSearch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start typing to search entries. + /// + internal static string BannerText { + get { + return ResourceManager.GetString("BannerText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy password. + /// + internal static string CopyPassword { + get { + return ResourceManager.GetString("CopyPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit entry. + /// + internal static string EditEntry { + get { + return ResourceManager.GetString("EditEntry", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Info { + get { + object obj = ResourceManager.GetObject("Info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Open entry url. + /// + internal static string OpenEntryUrl { + get { + return ResourceManager.GetString("OpenEntryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform entry auto-type. + /// + internal static string PerformAutoType { + get { + return ResourceManager.GetString("PerformAutoType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show entry in the main window. + /// + internal static string ShowEntry { + get { + return ResourceManager.GetString("ShowEntry", resourceCulture); + } + } + } +} diff --git a/AutoTypeSearch/Properties/Resources.resx b/AutoTypeSearch/Properties/Resources.resx new file mode 100755 index 0000000..76e9bce --- /dev/null +++ b/AutoTypeSearch/Properties/Resources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Global auto-type found no match for window: "{0}" + + + AutoTypeSearch + + + Start typing to search entries + + + Copy password + + + Edit entry + + + + ..\Info.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Open entry url + + + Perform entry auto-type + + + Show entry in the main window + + \ No newline at end of file diff --git a/AutoTypeSearch/Properties/Settings.Designer.cs b/AutoTypeSearch/Properties/Settings.Designer.cs new file mode 100755 index 0000000..62e2cdb --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.Designer.cs @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AutoTypeSearch.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTitle { + get { + return ((bool)(this["SearchTitle"])); + } + set { + this["SearchTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SearchUserName { + get { + return ((bool)(this["SearchUserName"])); + } + set { + this["SearchUserName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchUrl { + get { + return ((bool)(this["SearchUrl"])); + } + set { + this["SearchUrl"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchNotes { + get { + return ((bool)(this["SearchNotes"])); + } + set { + this["SearchNotes"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchCustomFields { + get { + return ((bool)(this["SearchCustomFields"])); + } + set { + this["SearchCustomFields"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SearchTags { + get { + return ((bool)(this["SearchTags"])); + } + set { + this["SearchTags"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CaseSensitive { + get { + return ((bool)(this["CaseSensitive"])); + } + set { + this["CaseSensitive"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] + public global::System.Drawing.Rectangle WindowPosition { + get { + return ((global::System.Drawing.Rectangle)(this["WindowPosition"])); + } + set { + this["WindowPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnFailedAutoType { + get { + return ((bool)(this["ShowOnFailedAutoType"])); + } + set { + this["ShowOnFailedAutoType"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOnHotKey { + get { + return ((bool)(this["ShowOnHotKey"])); + } + set { + this["ShowOnHotKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ShowOnIPC { + get { + return ((bool)(this["ShowOnIPC"])); + } + set { + this["ShowOnIPC"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ExcludeExpired { + get { + return ((bool)(this["ExcludeExpired"])); + } + set { + this["ExcludeExpired"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ResolveReferences { + get { + return ((bool)(this["ResolveReferences"])); + } + set { + this["ResolveReferences"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("PerformAutoType")] + public global::AutoTypeSearch.Actions DefaultAction { + get { + return ((global::AutoTypeSearch.Actions)(this["DefaultAction"])); + } + set { + this["DefaultAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("EditEntry")] + public global::AutoTypeSearch.Actions AlternativeAction { + get { + return ((global::AutoTypeSearch.Actions)(this["AlternativeAction"])); + } + set { + this["AlternativeAction"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("None")] + public global::System.Windows.Forms.Keys ShowHotKey { + get { + return ((global::System.Windows.Forms.Keys)(this["ShowHotKey"])); + } + set { + this["ShowHotKey"] = value; + } + } + } +} diff --git a/AutoTypeSearch/Properties/Settings.settings b/AutoTypeSearch/Properties/Settings.settings new file mode 100755 index 0000000..edcae1b --- /dev/null +++ b/AutoTypeSearch/Properties/Settings.settings @@ -0,0 +1,54 @@ + + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + \ No newline at end of file diff --git a/AutoTypeSearch/SearchResult.cs b/AutoTypeSearch/SearchResult.cs new file mode 100755 index 0000000..5af4177 --- /dev/null +++ b/AutoTypeSearch/SearchResult.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class SearchResult + { + private readonly PwDatabase mDatabase; + private readonly PwEntry mEntry; + private readonly string mFieldName; + private readonly int mStart; + private readonly int mLength; + private readonly string mFieldValue; + private readonly string mTitle; + private string mUniqueTitlePart; + private int mResultIndex = -1; + + public SearchResult(PwDatabase database, PwEntry entry, string title, string fieldName, string fieldValue, int start, int length) + { + mDatabase = database; + mEntry = entry; + mFieldName = fieldName; + mFieldValue = fieldValue; + mStart = start; + mLength = length; + mTitle = title; + + Debug.Assert(mLength >= 0 && mStart >= 0, "Negative values are invalid"); + Debug.Assert(mLength > 0 || mStart == 0, "Length must be non-zero (unless no highlight)"); + Debug.Assert((mStart + mLength) <= fieldValue.Length, "Length out of range"); + } + + public PwDatabase Database + { + get { return mDatabase; } + } + + public PwEntry Entry + { + get { return mEntry; } + } + + public string FieldName + { + get { return mFieldName; } + } + + public string FieldValue + { + get { return mFieldValue; } + } + + public int Start + { + get { return mStart; } + } + + public int Length + { + get { return mLength; } + } + + public string Title + { + get { return mTitle; } + } + + /// + /// The UniqueTitle may be modified from the to ensure uniqueness in the list of results + /// + public string UniqueTitle + { + get { return UniqueTitlePart + Title; } + } + + public string UniqueTitlePart + { + get { return mUniqueTitlePart; } + } + + public int ResultIndex + { + get { return mResultIndex; } + } + + public void SetResultIndex(int resultIndex) + { + if (mResultIndex != -1) + { + throw new InvalidOperationException("Result index has already been set"); + } + if (resultIndex < 0) + { + throw new ArgumentOutOfRangeException("resultIndex"); + } + + mResultIndex = resultIndex; + } + + /// + /// Sets by including parent group names to the specified depth. + /// + /// True if the group hierarchy is deep enough to support full requested + public bool SetUniqueTitleDepth(int depth) + { + var groupPath = new StringBuilder(); + var group = Entry.ParentGroup; + for (int i = 0; i < depth && group != null; i++) + { + groupPath.Insert(0, group.Name + " / "); + group = group.ParentGroup; + } + + mUniqueTitlePart = groupPath.ToString(); + + return group != null; + } + + + } +} diff --git a/AutoTypeSearch/SearchResults.cs b/AutoTypeSearch/SearchResults.cs new file mode 100755 index 0000000..b2b0529 --- /dev/null +++ b/AutoTypeSearch/SearchResults.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePass.Util.Spr; +using KeePassLib; +using KeePassLib.Utility; + +namespace AutoTypeSearch +{ + internal class SearchResults + { + private readonly string mTerm; + private readonly SearchResult[] mResults; + + private readonly object mLock = new object(); + private volatile int mCount; + private volatile bool mComplete; + + private readonly AutoResetEvent mResultsUpdated = new AutoResetEvent(false); + + private readonly CompareOptions mStringComparison; + private readonly bool mSearchTitle; + private readonly bool mSearchUserName; + private readonly bool mSearchUrl; + private readonly bool mSearchNotes; + private readonly bool mSearchCustomFields; + private readonly bool mResolveReferences; + private readonly bool mSearchTags; + + public SearchResults(int capacity, string term) + { + mTerm = term; + mResults = new SearchResult[capacity]; + + mStringComparison = Settings.Default.CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + mStringComparison |= CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreNonSpace; + mSearchTitle = Settings.Default.SearchTitle; + mSearchUserName = Settings.Default.SearchUserName; + mSearchUrl = Settings.Default.SearchUrl; + mSearchNotes = Settings.Default.SearchNotes; + mSearchCustomFields = Settings.Default.SearchCustomFields; + mSearchTags = Settings.Default.SearchTags; + mResolveReferences = Settings.Default.ResolveReferences; + } + + /// + /// Gets an ordered list of fields to search for the term + /// + /// + /// + private IEnumerable GetFieldsToSearch(PwEntry entry) + { + var fieldsToSearch = new List((int)entry.Strings.UCount); + if (mSearchTitle) fieldsToSearch.Add(PwDefs.TitleField); + if (mSearchUserName) fieldsToSearch.Add(PwDefs.UserNameField); + if (mSearchUrl) fieldsToSearch.Add(PwDefs.UrlField); + if (mSearchNotes) fieldsToSearch.Add(PwDefs.NotesField); + if (mSearchCustomFields) + { + foreach (var stringEntry in entry.Strings) + { + if (!stringEntry.Value.IsProtected && !PwDefs.IsStandardField(stringEntry.Key)) + { + fieldsToSearch.Add(stringEntry.Key); + } + } + } + if (mSearchTags) fieldsToSearch.Add(AutoTypeSearchExt.TagsVirtualFieldName); + + return fieldsToSearch; + } + + public void AddResultIfMatchesTerm(PwDatabase context, PwEntry entry) + { + // First try without resolving + var addedResult = AddResultIfMatchesTerm(context, entry, false); + + if (!addedResult && mResolveReferences) + { + // Not found without resolving, so try resolving + AddResultIfMatchesTerm(context, entry, true); + } + } + + private bool AddResultIfMatchesTerm(PwDatabase context, PwEntry entry, bool resolveReferences) + { + foreach (var fieldName in GetFieldsToSearch(entry)) + { + string fieldValue; + if (fieldName == AutoTypeSearchExt.TagsVirtualFieldName) + { + fieldValue = StrUtil.TagsToString(entry.Tags, true); + } + else + { + fieldValue = entry.Strings.ReadSafeEx(fieldName); + + if (resolveReferences) + { + fieldValue = ResolveReferences(context, entry, fieldValue); + } + } + + if (!String.IsNullOrEmpty(fieldValue)) + { + var foundIndex = CultureInfo.CurrentCulture.CompareInfo.IndexOf(fieldValue, mTerm, mStringComparison); + if (foundIndex >= 0) + { + // Found a match, create a search result and add it + AddResult(new SearchResult(context, entry, entry.Strings.ReadSafe(PwDefs.TitleField), fieldName, fieldValue, foundIndex, mTerm.Length)); + return true; + } + } + } + return false; + } + + /// + /// Resolves any references in the field value and returns it. If there were no references, + /// returns null (to avoid duplicate searching - it is assumed that the unresolved value has already been searched) + /// + private string ResolveReferences(PwDatabase context, PwEntry entry, string fieldValue) + { + if (fieldValue.IndexOf('{') < 0) + { + // Can't contain any references + return null; + } + + var sprContext = new SprContext(entry, context, SprCompileFlags.Deref) { ForcePlainTextPasswords = false }; + + var result = SprEngine.Compile(fieldValue, sprContext); + if (CultureInfo.CurrentCulture.CompareInfo.Compare(result,fieldValue, mStringComparison) == 0) + { + return null; + } + + return result; + } + + public void AddResultIfMatchesTerm(SearchResult candidate) + { + // First see whether the existing candidate is a further match in the same place + var fieldValue = candidate.FieldValue; + if (fieldValue.Length > candidate.Start + mTerm.Length && CultureInfo.CurrentCulture.CompareInfo.Compare(fieldValue.Substring(candidate.Start, mTerm.Length), mTerm, mStringComparison) == 0) + { + // Yep, match continues, so add it. + AddResult(new SearchResult(candidate.Database, candidate.Entry, candidate.Title, candidate.FieldName, fieldValue, candidate.Start, mTerm.Length)); + } + else + { + // Existing candidate match couldn't be extended, so search from scratch again + AddResultIfMatchesTerm(candidate.Database, candidate.Entry); + } + } + + private void AddResult(SearchResult result) + { + lock (mLock) + { + if (mComplete) + { + throw new InvalidOperationException("Search results have been completed"); + } + result.SetResultIndex(mCount); + mResults[mCount++] = result; + } + mResultsUpdated.Set(); + } + + /// + /// Indicates that the results are complete, and no more will be added. + /// + public void SetComplete() + { + lock (mLock) + { + mComplete = true; + } + mResultsUpdated.Set(); + } + + /// + /// Gets all the available results so far. + /// + /// Index to start returning from. Modified to be the first index not available yet on return. + /// Set to true if the results are complete, false if more results are pending but have not been returned. + /// + public SearchResult[] GetAvailableResults(ref int index, out bool complete) + { + int count; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + if (count <= index) + { + return new SearchResult[0]; + } + + var availableResults = new SearchResult[count - index]; + Array.Copy(mResults, index, availableResults, 0, availableResults.Length); + index = count; + + return availableResults; + } + + /// + /// Gets all the results. Will block until complete. + /// + /// + public IEnumerable GetAllResults() + { + int count = -1; + + for (var i = 0; i < mResults.Length; i++) + { + if (i > count) + { + // Reached the limit of availability so far, so see if more is available + do + { + bool moreAvailable, complete; + + lock (mLock) + { + moreAvailable = mCount > count; + count = mCount; + complete = mComplete; + } + + if (!moreAvailable) + { + if (complete) + { + // No more available, but the results are now complete anyway + yield break; + } + + // No more available yet, not yet complete, wait until more becomes available + mResultsUpdated.WaitOne(); + } + else + { + // More available now, so stop checking for more, continue with the loop to return them + break; + } + } while (true); + + Debug.Assert(i <= count, "More should be available now"); + } + + yield return mResults[i]; + } + } + + public SearchResults CreateChildResults(string term) + { + Debug.Assert(term.StartsWith(mTerm)); + + int count; + bool complete; + lock (mLock) + { + count = mCount; + complete = mComplete; + } + + // If complete, then we know we don't need more than count. Otherwise, it can't be more than this capacity anyway + var childCapacity = complete ? count : mResults.Length; + + return new SearchResults(childCapacity, term); + } + } +} diff --git a/AutoTypeSearch/SearchWindow.Designer.cs b/AutoTypeSearch/SearchWindow.Designer.cs new file mode 100755 index 0000000..18b37d1 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.Designer.cs @@ -0,0 +1,201 @@ +using System.Windows.Forms; + +namespace AutoTypeSearch +{ + partial class SearchWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mSearch = new System.Windows.Forms.TextBox(); + this.mResults = new System.Windows.Forms.ListBox(); + this.mLayout = new System.Windows.Forms.TableLayoutPanel(); + this.mBanner = new System.Windows.Forms.PictureBox(); + this.mInfoBanner = new System.Windows.Forms.Panel(); + this.mInfoLabel = new System.Windows.Forms.Label(); + this.mInfoBannerImage = new System.Windows.Forms.PictureBox(); + this.mThrobber = new System.Windows.Forms.PictureBox(); + this.mResultsUpdater = new System.Windows.Forms.Timer(this.components); + this.mNoResultsLabel = new System.Windows.Forms.Label(); + this.mLayout.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).BeginInit(); + this.mInfoBanner.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).BeginInit(); + this.SuspendLayout(); + // + // mSearch + // + this.mSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.mSearch.Location = new System.Drawing.Point(1, 78); + this.mSearch.Margin = new System.Windows.Forms.Padding(1, 0, 1, 0); + this.mSearch.Name = "mSearch"; + this.mSearch.Size = new System.Drawing.Size(521, 20); + this.mSearch.TabIndex = 0; + this.mSearch.LocationChanged += new System.EventHandler(this.mSearch_LocationChanged); + this.mSearch.TextChanged += new System.EventHandler(this.mSearch_TextChanged); + // + // mResults + // + this.mResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.mResults.Dock = System.Windows.Forms.DockStyle.Fill; + this.mResults.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.mResults.FormattingEnabled = true; + this.mResults.IntegralHeight = false; + this.mResults.Location = new System.Drawing.Point(0, 98); + this.mResults.Margin = new System.Windows.Forms.Padding(0); + this.mResults.Name = "mResults"; + this.mResults.Size = new System.Drawing.Size(523, 176); + this.mResults.TabIndex = 1; + this.mResults.TabStop = false; + this.mResults.MouseClick += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseClick); + this.mResults.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.mResults_DrawItem); + this.mResults.LocationChanged += new System.EventHandler(this.mResults_LocationChanged); + this.mResults.MouseEnter += new System.EventHandler(this.mResults_MouseEnter); + this.mResults.MouseMove += new System.Windows.Forms.MouseEventHandler(this.mResults_MouseMove); + // + // mLayout + // + this.mLayout.ColumnCount = 1; + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.mLayout.Controls.Add(this.mSearch, 0, 2); + this.mLayout.Controls.Add(this.mResults, 0, 3); + this.mLayout.Controls.Add(this.mBanner, 0, 0); + this.mLayout.Controls.Add(this.mInfoBanner, 0, 1); + this.mLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLayout.Location = new System.Drawing.Point(0, 0); + this.mLayout.Name = "mLayout"; + this.mLayout.RowCount = 4; + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.mLayout.Size = new System.Drawing.Size(523, 274); + this.mLayout.TabIndex = 2; + // + // mBanner + // + this.mBanner.Dock = System.Windows.Forms.DockStyle.Top; + this.mBanner.Location = new System.Drawing.Point(0, 0); + this.mBanner.Margin = new System.Windows.Forms.Padding(0); + this.mBanner.Name = "mBanner"; + this.mBanner.Size = new System.Drawing.Size(523, 60); + this.mBanner.TabIndex = 3; + this.mBanner.TabStop = false; + this.mBanner.MouseDown += new System.Windows.Forms.MouseEventHandler(this.mBannerImage_MouseDown); + // + // mInfoBanner + // + this.mInfoBanner.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mInfoBanner.BackColor = System.Drawing.SystemColors.Info; + this.mInfoBanner.Controls.Add(this.mInfoLabel); + this.mInfoBanner.Controls.Add(this.mInfoBannerImage); + this.mInfoBanner.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoBanner.Location = new System.Drawing.Point(2, 61); + this.mInfoBanner.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1); + this.mInfoBanner.Name = "mInfoBanner"; + this.mInfoBanner.Size = new System.Drawing.Size(520, 16); + this.mInfoBanner.TabIndex = 8; + // + // mInfoLabel + // + this.mInfoLabel.AutoEllipsis = true; + this.mInfoLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mInfoLabel.ForeColor = System.Drawing.SystemColors.InfoText; + this.mInfoLabel.Location = new System.Drawing.Point(16, 0); + this.mInfoLabel.Name = "mInfoLabel"; + this.mInfoLabel.Size = new System.Drawing.Size(504, 16); + this.mInfoLabel.TabIndex = 6; + this.mInfoLabel.Text = "AutoType failed to find"; + // + // mInfoBannerImage + // + this.mInfoBannerImage.Dock = System.Windows.Forms.DockStyle.Left; + this.mInfoBannerImage.Image = global::AutoTypeSearch.Properties.Resources.Info; + this.mInfoBannerImage.Location = new System.Drawing.Point(0, 0); + this.mInfoBannerImage.Margin = new System.Windows.Forms.Padding(0); + this.mInfoBannerImage.Name = "mInfoBannerImage"; + this.mInfoBannerImage.Size = new System.Drawing.Size(16, 16); + this.mInfoBannerImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mInfoBannerImage.TabIndex = 7; + this.mInfoBannerImage.TabStop = false; + // + // mThrobber + // + this.mThrobber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.mThrobber.BackColor = System.Drawing.SystemColors.Window; + this.mThrobber.Location = new System.Drawing.Point(503, 81); + this.mThrobber.Name = "mThrobber"; + this.mThrobber.Size = new System.Drawing.Size(16, 16); + this.mThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.mThrobber.TabIndex = 4; + this.mThrobber.TabStop = false; + this.mThrobber.Visible = false; + // + // mResultsUpdater + // + this.mResultsUpdater.Interval = 250; + this.mResultsUpdater.Tick += new System.EventHandler(this.mResultsUpdater_Tick); + // + // mNoResultsLabel + // + this.mNoResultsLabel.AutoSize = true; + this.mNoResultsLabel.Location = new System.Drawing.Point(5, 103); + this.mNoResultsLabel.Name = "mNoResultsLabel"; + this.mNoResultsLabel.Size = new System.Drawing.Size(84, 13); + this.mNoResultsLabel.TabIndex = 5; + this.mNoResultsLabel.Text = "No results found"; + // + // SearchWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Window; + this.ClientSize = new System.Drawing.Size(523, 274); + this.ControlBox = false; + this.Controls.Add(this.mNoResultsLabel); + this.Controls.Add(this.mThrobber); + this.Controls.Add(this.mLayout); + this.MinimumSize = new System.Drawing.Size(160, 96); + this.Name = "SearchWindow"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.mLayout.ResumeLayout(false); + this.mLayout.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mBanner)).EndInit(); + this.mInfoBanner.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mInfoBannerImage)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.mThrobber)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox mSearch; + private System.Windows.Forms.ListBox mResults; + private System.Windows.Forms.TableLayoutPanel mLayout; + private System.Windows.Forms.PictureBox mBanner; + private PictureBox mThrobber; + private Timer mResultsUpdater; + private Label mNoResultsLabel; + private Label mInfoLabel; + private Panel mInfoBanner; + private PictureBox mInfoBannerImage; + } +} \ No newline at end of file diff --git a/AutoTypeSearch/SearchWindow.cs b/AutoTypeSearch/SearchWindow.cs new file mode 100755 index 0000000..363b898 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.cs @@ -0,0 +1,925 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Media; +using System.Reflection; +using System.Text; +using System.Windows.Forms; +using AutoTypeSearch.Properties; +using KeePass.Forms; +using KeePass.Resources; +using KeePass.UI; +using KeePass.Util; +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Native; + +namespace AutoTypeSearch +{ + public partial class SearchWindow : Form + { + private const int SecondLineInset = 10; + + // HACK to work around mono bug + private static readonly FieldInfo sMonoListBoxTopIndex = typeof(ListBox).GetField("top_index", BindingFlags.Instance | BindingFlags.NonPublic); + + private readonly MainForm mMainForm; + private readonly Bitmap mBannerImage; + private readonly Searcher mSearcher; + + private readonly Stream mThrobberImageStream; + + private int? mWindowTopBorderHeight; + private int mBannerWidth = -1; + private int mMaximumExpandHeight; + private bool mManualSizeApplied; + private SearchResults mCurrentSearch; + private SearchResults mLastResultsUpdated; + private int mLastResultsUpdatedNextAvailableIndex; + + #region Opening + public SearchWindow() + { + InitializeComponent(); + + // Mono can't load animated gifs from resx without crashing, so load it from an embedded resource instead + try + { + mThrobberImageStream = GetType().Assembly.GetManifestResourceStream("AutoTypeSearch.Throbber.gif"); + if (mThrobberImageStream != null) + { + mThrobber.Image = Image.FromStream(mThrobberImageStream); + } + } + catch (Exception ex) + { + Debug.Fail("Failed to load Throbber.gif from embedded resource: " + ex.Message); + } + + GlobalWindowManager.CustomizeControl(this); + UIUtil.SetExplorerTheme(mResults, true); + SetItemHeight(); + } + + public SearchWindow(MainForm mainForm, string infoBanner) : this() + { + mMainForm = mainForm; + + mInfoBanner.Height = Math.Max(mInfoBannerImage.Height, mInfoLabel.Font.Height) + mInfoBanner.Margin.Vertical; + mInfoLabel.Padding = new Padding(0, (mInfoBanner.Height - mInfoLabel.Font.Height) / 2, 0, 0); + mInfoLabel.Text = infoBanner; + + if (infoBanner == null) + { + mInfoBanner.Visible = false; + mInfoBanner.Height = 0; + } + + mSearcher = new Searcher(mMainForm.DocumentManager.GetOpenDatabases().ToArray()); + + Icon = mMainForm.Icon; + using (var bannerIcon = new Icon(Icon, 48, 48)) + { + mBannerImage = bannerIcon.ToBitmap(); + } + UpdateBanner(); + + ShowThrobber = false; + + FontUtil.AssignDefaultItalic(mNoResultsLabel); + } + + + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (NativeMethods.IsWindows10()) + { + mWindowTopBorderHeight = PointToScreen(Point.Empty).Y - this.Top; + NativeMethods.RefreshWindowFrame(Handle); + } + + var windowRect = Settings.Default.WindowPosition; + var collapsedWindowRect = windowRect; + + collapsedWindowRect.Height = mSearch.Bottom + (Height - ClientSize.Height); + + MinimumSize = new Size(MinimumSize.Width, collapsedWindowRect.Height); + + if (windowRect.IsEmpty || !IsOnScreen(collapsedWindowRect)) + { + windowRect = new Rectangle(0, 0, Width, Height); + Height = collapsedWindowRect.Height; + + CenterToScreen(); + } + else + { + Location = windowRect.Location; + Size = collapsedWindowRect.Size; + } + + mMaximumExpandHeight = Math.Max(windowRect.Height, MinimumSize.Height + mResults.ItemHeight); + } + + + private static bool IsOnScreen(Rectangle rectangle) + { + return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rectangle)); + } + + private void SetItemHeight() + { + mResults.ItemHeight = mResults.Font.Height * 2 + 2; + } + + protected override void WndProc(ref Message m) + { + if (mWindowTopBorderHeight.HasValue) + { + NativeMethods.RemoveWindowFrameTopBorder(ref m, mWindowTopBorderHeight.Value); + } + base.WndProc(ref m); + } + + #endregion + + #region Closing + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + Deactivate += OnDeactivate; + } + + private void OnDeactivate(object sender, EventArgs eventArgs) + { + Close(); + } + + protected override void OnClosed(EventArgs e) + { + Deactivate -= OnDeactivate; + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + if (mBannerImage != null) + { + mBannerImage.Dispose(); + } + if (mThrobber.Image != null) + { + mThrobber.Image.Dispose(); + mThrobber.Image = null; + mThrobberImageStream.Dispose(); + } + components.Dispose(); + } + base.Dispose(disposing); + } + #endregion + + #region Item Drawing + private void mResults_DrawItem(object sender, DrawItemEventArgs e) + { + var searchResult = mResults.Items[e.Index] as SearchResult; + if (searchResult == null) + { + Debug.Fail("Unexpected item in mResults"); +// ReSharper disable once HeuristicUnreachableCode - Not unreachable + return; + } + var drawingArea = e.Bounds; + drawingArea.Height--; // Leave room for a dividing line at the bottom + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + DrawBorderedRectangle(e.Graphics, drawingArea, SystemColors.Highlight); + } + else + { + e.Graphics.FillRectangle(SystemBrushes.Window, drawingArea); + } + + var image = GetImage(searchResult.Database, searchResult.Entry.CustomIconUuid, searchResult.Entry.IconId); + var imageMargin = (drawingArea.Height - image.Height) / 2; + e.Graphics.DrawImage(image, drawingArea.Left + imageMargin, drawingArea.Top + imageMargin, image.Width, image.Height); + + var textLeftMargin = drawingArea.Left + imageMargin * 2 + image.Width; + var textBounds = new Rectangle(textLeftMargin, drawingArea.Top + 1, drawingArea.Width - textLeftMargin - 1, drawingArea.Height - 2); + + var line1Bounds = textBounds; + line1Bounds.Height = e.Font.Height; + var line2Bounds = line1Bounds; + line2Bounds.Y += line2Bounds.Height - 1; + line2Bounds.X += SecondLineInset; + line2Bounds.Width -= SecondLineInset; + + var resultInTitleField = searchResult.FieldName == PwDefs.TitleField; + + var title = (resultInTitleField ? searchResult.FieldValue : searchResult.Title).Replace('\n', ' '); // The FieldValue may have references resolved, whereas the title is always read directly. + + var uniqueTitlePartWidth = 0; + if (!String.IsNullOrEmpty(searchResult.UniqueTitlePart)) + { + var uniqueTitlePart = searchResult.UniqueTitlePart.Replace('\n', ' '); + + var titleWidth = TextRenderer.MeasureText(e.Graphics, title, e.Font, line1Bounds.Size, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + + var availableWidthForUniqueTitlePart = line1Bounds.Width - titleWidth; + if (availableWidthForUniqueTitlePart > 20) // Don't bother including a unique part if there's no room for it + { + var uniqueTitlePartReversed = ReverseString(uniqueTitlePart); + + uniqueTitlePartWidth = TextRenderer.MeasureText(e.Graphics, uniqueTitlePartReversed, e.Font, new Size(availableWidthForUniqueTitlePart, line1Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + uniqueTitlePart = ReverseString(uniqueTitlePartReversed); + + TextRenderer.DrawText(e.Graphics, uniqueTitlePart, e.Font, new Rectangle(line1Bounds.X, line1Bounds.Y, uniqueTitlePartWidth, line1Bounds.Height), SystemColors.GrayText, TextFormatFlags.NoPadding); + } + } + + var titleBounds = new Rectangle(line1Bounds.X + uniqueTitlePartWidth, line1Bounds.Y, line1Bounds.Width - uniqueTitlePartWidth, line1Bounds.Height); + + if (resultInTitleField) + { + // Found the result in the title field. Highlight title in first line. + DrawHighlight(e, titleBounds, title, searchResult.Start, searchResult.Length); + } + + TextRenderer.DrawText(e.Graphics, searchResult.Title, e.Font, titleBounds, SystemColors.WindowText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + + if (resultInTitleField) + { + // Found the result in the title field. Use Username for second line. + TextRenderer.DrawText(e.Graphics, KPRes.UserName + ": " + searchResult.Entry.Strings.ReadSafeEx(PwDefs.UserNameField), e.Font, line2Bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + else + { + // Found the result in not title field. Show the matching result on second line + + var fieldValue = searchResult.FieldValue.Replace('\n',' '); + var fieldNamePrefix = GetDisplayFieldName(searchResult.FieldName) + ": "; + + var remainingSpace = line2Bounds.Width; + var fieldNamePrefixWidth = TextRenderer.MeasureText(e.Graphics, fieldNamePrefix, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldNamePrefixWidth; + + int fieldValueHighlightWidth = 0, fieldValueLeftContextWidth = 0, fieldValueRightContextWidth = 0; + + var leftContext = fieldValue.Substring(0, searchResult.Start); + var highlight = fieldValue.Substring(searchResult.Start, searchResult.Length); + var rightContext = fieldValue.Substring(searchResult.Start + searchResult.Length); + + if (searchResult.Length == 0) + { + fieldValueHighlightWidth = remainingSpace; + } + else + { + if (remainingSpace > 0) + { + var availableSpace = remainingSpace; + fieldValueHighlightWidth = TextRenderer.MeasureText(e.Graphics, highlight, e.Font, new Size(availableSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + remainingSpace -= fieldValueHighlightWidth; + } + + // Of the space remaining, divide it equally between that which comes before, and that which comes after + if (!String.IsNullOrEmpty(leftContext)) + { + var leftContextReversed = ReverseString(leftContext); + fieldValueLeftContextWidth = TextRenderer.MeasureText(e.Graphics, leftContextReversed, e.Font, new Size(remainingSpace / 2, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis | TextFormatFlags.ModifyString).Width; + + if (fieldValueLeftContextWidth > remainingSpace) + { + // Always allow space for the minimal left context + fieldValueHighlightWidth -= (fieldValueLeftContextWidth - remainingSpace); + remainingSpace = 0; + } + else + { + remainingSpace -= fieldValueLeftContextWidth; + } + + // Replace left context with the truncated reversed left context. + leftContext = ReverseString(leftContextReversed); + } + + if (remainingSpace > 0 && !String.IsNullOrEmpty(rightContext)) + { + fieldValueRightContextWidth = TextRenderer.MeasureText(e.Graphics, rightContext, e.Font, new Size(remainingSpace, line2Bounds.Height), TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis).Width; + if (fieldValueRightContextWidth > remainingSpace) + { + fieldValueRightContextWidth = 0; + } + } + } + + // Now draw it all + var bounds = line2Bounds; + bounds.Width = fieldNamePrefixWidth; + TextRenderer.DrawText(e.Graphics, fieldNamePrefix, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + if (fieldValueLeftContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueLeftContextWidth; + TextRenderer.DrawText(e.Graphics, leftContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding); // No ellipsis as the leftContext string has already been truncated appropriately + } + if (fieldValueHighlightWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueHighlightWidth; + + if (searchResult.Length > 0) + { + DrawHighlightRectangle(e, bounds); + } + TextRenderer.DrawText(e.Graphics, highlight, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + if (fieldValueRightContextWidth > 0) + { + bounds.X += bounds.Width; + bounds.Width = fieldValueRightContextWidth; + TextRenderer.DrawText(e.Graphics, rightContext, e.Font, bounds, SystemColors.GrayText, TextFormatFlags.NoPadding | TextFormatFlags.EndEllipsis); + } + } + + e.Graphics.DrawLine(SystemPens.ButtonFace, drawingArea.Left, drawingArea.Bottom, drawingArea.Right, drawingArea.Bottom); + } + + private static string ReverseString(string value) + { + return new String(value.ToCharArray().TakeWhile(c => c != '\0').Reverse().ToArray()); + } + + private static void DrawHighlight(DrawItemEventArgs e, Rectangle lineBounds, string text, int highlightFrom, int highlightLength) + { + var highlightX = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width; + var highlightWidth = TextRenderer.MeasureText(e.Graphics, text.Substring(0, highlightFrom + highlightLength), e.Font, Size.Empty, TextFormatFlags.NoPadding).Width - highlightX; + + DrawHighlightRectangle(e, new Rectangle(lineBounds.Left + highlightX, lineBounds.Top, highlightWidth, lineBounds.Height)); + } + + private static void DrawHighlightRectangle(DrawItemEventArgs e, Rectangle rectangle) + { + DrawBorderedRectangle(e.Graphics, rectangle, Color.PaleTurquoise); + } + + private static void DrawBorderedRectangle(Graphics graphics, Rectangle rectangle, Color colour) + { + var border = rectangle; + border.Width--; + border.Height--; + + using (var brush = new SolidBrush(MergeColors(colour, SystemColors.Window, 0.2))) + { + graphics.FillRectangle(brush, rectangle); + } + using (var pen = new Pen(colour, 1f)) + { + graphics.DrawRectangle(pen, border); + } + } + + private Image GetImage(PwDatabase database, PwUuid customIconId, PwIcon iconId) + { + Image image = null; + if (!customIconId.Equals(PwUuid.Zero)) + { + image = database.GetCustomIcon(customIconId, DpiUtil.ScaleIntX(16), DpiUtil.ScaleIntY(16)); + } + if (image == null) + { + try { image = mMainForm.ClientIcons.Images[(int)iconId]; } + catch (Exception) { Debug.Assert(false); } + } + + return image; + } + + private static string GetDisplayFieldName(string fieldName) + { + switch (fieldName) + { + case PwDefs.TitleField: + return KPRes.Title; + case PwDefs.UserNameField: + return KPRes.UserName; + case PwDefs.PasswordField: + return KPRes.Password; + case PwDefs.UrlField: + return KPRes.Url; + case PwDefs.NotesField: + return KPRes.Notes; + case AutoTypeSearchExt.TagsVirtualFieldName: + return KPRes.Tags; + default: + return fieldName; + } + } + + public static Color MergeColors(Color from, Color to, double amount) + { + var r = (byte)((from.R * amount) + to.R * (1 - amount)); + var g = (byte)((from.G * amount) + to.G * (1 - amount)); + var b = (byte)((from.B * amount) + to.B * (1 - amount)); + return Color.FromArgb(r, g, b); + } + #endregion + + #region Mouse tracking + private Point mMouseEntryPosition; + + private void mResults_MouseEnter(object sender, EventArgs e) + { + mMouseEntryPosition = MousePosition; + } + + private void mResults_MouseMove(object sender, MouseEventArgs e) + { + // Discard the location the mouse has on entering the control (as it may be that the control has just moved under the mouse, not the other way around) + if (MousePosition == mMouseEntryPosition) + { + return; + } + + // Hot tracking + var hoverIndex = mResults.IndexFromPoint(e.X, e.Y); + if (hoverIndex >= 0 && mResults.SelectedIndex != hoverIndex) + { + if (mResults.GetItemRectangle(hoverIndex).Bottom <= mResults.ClientRectangle.Bottom) + { + mResults.SelectedIndex = hoverIndex; + } + else + { + // Avoid the control scrolling + mResults.BeginUpdate(); + var topIndex = mResults.TopIndex; + mResults.SelectedIndex = hoverIndex; + mResults.TopIndex = topIndex; + mResults.EndUpdate(); + } + } + } + #endregion + + #region Resizing + protected override void OnResizeBegin(EventArgs e) + { + // Stop automatically sizing - the user is picking a size they want. + mManualSizeApplied = true; + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateBanner(); + + mResults.Invalidate(); + } + + protected override void OnResizeEnd(EventArgs e) + { + base.OnResizeEnd(e); + + if (Height > MinimumSize.Height && Height != mMaximumExpandHeight) + { + mMaximumExpandHeight = Math.Max(Height, MinimumSize.Height + mResults.ItemHeight); + } + else + { + mManualSizeApplied = false; + } + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + + private void UpdateBanner() + { + if (mBannerImage != null) + { + BannerFactory.UpdateBanner(this, mBanner, mBannerImage, PwDefs.ProductName, Resources.BannerText, ref mBannerWidth); + } + } + + private void mSearch_LocationChanged(object sender, EventArgs e) + { + mThrobber.Location = new Point(mSearch.Right - mThrobber.Width - mThrobber.Margin.Right, mSearch.Top + (mSearch.Height - mThrobber.Height) / 2); + } + + private void mResults_LocationChanged(object sender, EventArgs e) + { + mNoResultsLabel.Top = mResults.Top + (mResults.ItemHeight - mNoResultsLabel.Height) / 2; + } + #endregion + + #region Searching + private static readonly SearchResultPrecedence SearchResultPrecedenceComparer = new SearchResultPrecedence(); + private void mSearch_TextChanged(object sender, EventArgs e) + { + if (mSearch.Text.Length < 2) + { + // Stop searching + mResultsUpdater.Enabled = false; + ShowThrobber = false; + Height = MinimumSize.Height; + mManualSizeApplied = false; + mResults.Items.Clear(); + mLastResultsUpdated = null; + mLastResultsUpdatedNextAvailableIndex = 0; + } + else + { + // Start searching + mNoResultsLabel.Visible = false; + mCurrentSearch = mSearcher.Search(mSearch.Text); + mResultsUpdater.Enabled = true; + ShowThrobber = true; + mResultsUpdater_Tick(null, EventArgs.Empty); // Quick poke just in case the results are already done. + } + } + + [SuppressMessage("ReSharper", "CoVariantArrayConversion", Justification = "Object arrays for Listbox.Items, known to be of correct type")] + private void mResultsUpdater_Tick(object sender, EventArgs e) + { + if (mLastResultsUpdated != mCurrentSearch) + { + // Clear out old results and replace with new ones + mResults.Items.Clear(); + mLastResultsUpdated = mCurrentSearch; + mLastResultsUpdatedNextAvailableIndex = 0; + } + var existingResultsCount = mResults.Items.Count; + + bool complete; + var newResults = mLastResultsUpdated.GetAvailableResults(ref mLastResultsUpdatedNextAvailableIndex, out complete); + if (newResults.Length > 0) + { + mResults.BeginUpdate(); + + SearchResult[] allResults; + if (existingResultsCount > 0) + { + allResults = new SearchResult[existingResultsCount + newResults.Length]; + mResults.Items.CopyTo(allResults, 0); + newResults.CopyTo(allResults, existingResultsCount); + + mResults.Items.Clear(); + } + else + { + allResults = newResults; + } + + CalculateUniqueTitles(allResults); + + Array.Sort(allResults, SearchResultPrecedenceComparer); + mResults.Items.AddRange(allResults); + + mResults.EndUpdate(); + + if (allResults.Length > 0) + { + if (mResults.SelectedIndex == -1) + { + try + { + // HACK to work around mono bug + if (sMonoListBoxTopIndex != null) + { + sMonoListBoxTopIndex.SetValue(mResults, 1); // Set the top_index to 1 so that when selected index is set to 0, and calls EnsureVisible(0), it follows the index < top_index pass and not the broken index >= top_index + rows path. + } + + mResults.SelectedIndex = 0; + mResults.TopIndex = 0; + } + catch (Exception ex) + { + Debug.Fail("Failed to set selection on count of " + allResults.Length + ": " + ex.Message); + } + } + + if (!mManualSizeApplied) + { + Height = Math.Min(mMaximumExpandHeight, MinimumSize.Height + (allResults.Length * mResults.ItemHeight)); + } + } + } + + if (complete) + { + ShowThrobber = false; + mResultsUpdater.Enabled = false; + + if (mResults.Items.Count == 0) + { + mNoResultsLabel.Visible = true; + Height = MinimumSize.Height + mResults.ItemHeight; + mManualSizeApplied = false; + } + } + } + + private void CalculateUniqueTitles(IEnumerable results, int depth = 0) + { + // Where results have identical titles, include group titles to make them unique + depth += 1; + + // First create a lookup by title + var titles = new Dictionary>(); + foreach (var searchResult in results) + { + List resultsWithSameTitle; + if (titles.TryGetValue(searchResult.UniqueTitle, out resultsWithSameTitle)) + { + resultsWithSameTitle.Add(searchResult); + } + else + { + titles.Add(searchResult.UniqueTitle, new List { searchResult }); + } + } + + // Attempt to unique-ify any non-unique titles + foreach (var resultsSharingTitle in titles.Values) + { + if (resultsSharingTitle.Count > 1) + { + var titlesModified = false; + foreach (var searchResult in resultsSharingTitle) + { + titlesModified |= searchResult.SetUniqueTitleDepth(depth); + } + + if (titlesModified) + { + // Recurse in case of continuing non-uniqueness + CalculateUniqueTitles(resultsSharingTitle, depth); + } + } + } + } + + private class SearchResultPrecedence : IComparer + { + public int Compare(SearchResult x, SearchResult y) + { + // First precedence is that if the result is the start of the field value, it's higher precedence than if it doesn't. + var result = -(x.Start == 0).CompareTo(y.Start == 0); + + // Second precedence is that the start of the title field is higher precedence than the start of any other field + if (result == 0) + { + result = -(x.FieldName == PwDefs.TitleField).CompareTo(y.FieldName == PwDefs.TitleField); + } + + // Both start the title field, so both equal. Have to have consistent ordering, so return final precedence based search index + if (result == 0) + { + result = x.ResultIndex.CompareTo(y.ResultIndex); + } + + return result; + } + } + + private bool ShowThrobber + { + get { return mThrobber.Visible; } + set + { + if (value != ShowThrobber) + { + if (value) + { + mThrobber.Visible = true; + + // Set the margin on the textbox to allow room for the throbber + NativeMethods.SetTextBoxRightMargin(mSearch, mThrobber.Width + mThrobber.Margin.Right); + } + else + { + mThrobber.Visible = false; + + NativeMethods.SetTextBoxRightMargin(mSearch, 0); + } + } + } + } + #endregion + + private void mBannerImage_MouseDown(object sender, MouseEventArgs e) + { + // Allow drag by banner image + if (e.Button == MouseButtons.Left) + { + if (e.Clicks == 2) + { + // Re-center the form on double-click + CenterToScreen(); + + Settings.Default.WindowPosition = new Rectangle(Left, Top, Width, mMaximumExpandHeight); + } + else if (!NativeLib.IsUnix()) + { + NativeMethods.StartFormDrag(this); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape: + Close(); + return true; + case Keys.Up: + TryChangeSelection(-1); + return true; + case Keys.Down: + TryChangeSelection(1); + return true; + case Keys.PageUp: + TryChangeSelection(-mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.PageDown: + TryChangeSelection(mResults.ClientSize.Height / mResults.ItemHeight); + return true; + case Keys.Home | Keys.Control: + mResults.SelectedIndex = 0; + return true; + case Keys.End | Keys.Control: + mResults.SelectedIndex = mResults.Items.Count - 1; + return true; + case Keys.Enter: + PerformAction(Settings.Default.DefaultAction, mResults.SelectedItem as SearchResult); + break; + case Keys.Enter | Keys.Shift: + PerformAction(Settings.Default.AlternativeAction, mResults.SelectedItem as SearchResult); + break; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + #region Selection Changing + + protected override void OnMouseWheel(MouseEventArgs e) + { + mResults.TopIndex -= (e.Delta / Math.Abs(e.Delta)); + } + + private void TryChangeSelection(int delta) + { + if (mResults.Items.Count > 0) + { + mResults.SelectedIndex = Math.Max(Math.Min(mResults.Items.Count - 1, mResults.SelectedIndex + delta), 0); + } + } + #endregion + + #region Actions + + private void mResults_MouseClick(object sender, MouseEventArgs e) + { + var clickIndex = mResults.IndexFromPoint(e.X, e.Y); + if (clickIndex >= 0) + { + var clickedResult = mResults.Items[clickIndex] as SearchResult; + if (clickedResult != null) + { + PerformAction((ModifierKeys & Keys.Shift) == Keys.Shift ? Settings.Default.AlternativeAction : Settings.Default.DefaultAction, clickedResult); + } + } + } + + private void PerformAction(Actions action, SearchResult searchResult) + { + Close(); + + if (searchResult != null) + { + switch (action) + { + case Actions.PerformAutoType: + AutoTypeEntry(searchResult); + break; + case Actions.EditEntry: + EditEntry(searchResult); + break; + case Actions.ShowEntry: + ShowEntry(searchResult); + break; + case Actions.OpenEntryUrl: + OpenEntryUrl(searchResult); + break; + case Actions.CopyPassword: + CopyPassword(searchResult); + break; + default: + throw new ArgumentOutOfRangeException("action"); + } + } + } + + private void AutoTypeEntry(SearchResult searchResult) + { + bool result; + if (ActiveForm != null) + { + result = AutoType.PerformIntoPreviousWindow(mMainForm, searchResult.Entry, searchResult.Database); + } + else + { + result = AutoType.PerformIntoCurrentWindow(searchResult.Entry, searchResult.Database); + } + if (!result) + { + SystemSounds.Beep.Play(); + + if (Settings.Default.AlternativeAction != Actions.PerformAutoType) + { + PerformAction(Settings.Default.AlternativeAction, searchResult); + } + } + } + + private void EditEntry(SearchResult searchResult) + { + using (var entryForm = new PwEntryForm()) + { + mMainForm.MakeDocumentActive(mMainForm.DocumentManager.FindDocument(searchResult.Database)); + + entryForm.InitEx(searchResult.Entry, PwEditMode.EditExistingEntry, searchResult.Database, mMainForm.ClientIcons, false, false); + + ShowForegroundDialog(entryForm); + + mMainForm.UpdateUI(false, null, searchResult.Database.UINeedsIconUpdate, null, true, null, entryForm.HasModifiedEntry); + } + } + +// ReSharper disable once UnusedMethodReturnValue.Local - Generic helper, result may be used in future + private DialogResult ShowForegroundDialog(Form form) + { + mMainForm.EnsureVisibleForegroundWindow(false, false); + form.StartPosition = FormStartPosition.CenterScreen; + if (mMainForm.IsTrayed()) + { + form.ShowInTaskbar = true; + } + + form.Shown += ActivateFormOnShown; + return form.ShowDialog(mMainForm); + } + + private static void ActivateFormOnShown(object sender, EventArgs eventArgs) + { + var form = (Form)sender; + form.Shown -= ActivateFormOnShown; + form.Activate(); + } + + private void ShowEntry(SearchResult searchResult) + { + // Show this entry + mMainForm.UpdateUI(false, mMainForm.DocumentManager.FindDocument(searchResult.Database), true, searchResult.Entry.ParentGroup, true, null, false, null); + mMainForm.SelectEntries(new PwObjectList { searchResult.Entry }, true, true); + mMainForm.EnsureVisibleEntry(searchResult.Entry.Uuid); + mMainForm.UpdateUI(false, null, false, null, false, null, false); + mMainForm.EnsureVisibleForegroundWindow(true, true); + } + + private void OpenEntryUrl(SearchResult searchResult) + { + WinUtil.OpenEntryUrl(searchResult.Entry); + } + + private void CopyPassword(SearchResult searchResult) + { + if (ClipboardUtil.Copy(searchResult.Entry.Strings.ReadSafe(PwDefs.PasswordField), true, true, searchResult.Entry, + mMainForm.DocumentManager.SafeFindContainerOf(searchResult.Entry), + IntPtr.Zero)) + { + mMainForm.StartClipboardCountdown(); + } + } + + #endregion + } +} diff --git a/AutoTypeSearch/SearchWindow.resx b/AutoTypeSearch/SearchWindow.resx new file mode 100755 index 0000000..8ef82f0 --- /dev/null +++ b/AutoTypeSearch/SearchWindow.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/AutoTypeSearch/Searcher.cs b/AutoTypeSearch/Searcher.cs new file mode 100755 index 0000000..433ae94 --- /dev/null +++ b/AutoTypeSearch/Searcher.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using AutoTypeSearch.Properties; +using KeePassLib; + +namespace AutoTypeSearch +{ + internal class Searcher + { + private readonly PwDatabase[] mDatabases; + private readonly Dictionary mSearches = new Dictionary(); + + public Searcher(PwDatabase[] databases) + { + mDatabases = databases; + } + + public SearchResults Search(string term) + { + if (term.Length < 2) + { + throw new ArgumentException("Search term must be at least 2 characters"); + } + + SearchResults parentResults = null; + + var termParent = term; + while (termParent.Length >= 2) + { + if (mSearches.TryGetValue(termParent, out parentResults)) + { + if (termParent == term) + { + // This is an exact duplicate search, so return it. + return parentResults; + } + + // Found an existing search for a parent of the term, start from there. + break; + } + + // No existing search for termParent found, try less. + termParent = termParent.Remove(termParent.Length - 1, 1); + } + + SearchResults searchResults; + if (parentResults == null) + { + // No parent found at all, start from scratch + searchResults = new SearchResults(GetCountOfAllDatabaseEntries(), term); + + var rootSearchThread = new Thread(RootSearchWorker) { Name = term }; + rootSearchThread.Start(searchResults); + } + else + { + searchResults = parentResults.CreateChildResults(term); + + var childSearchThread = new Thread(ChildSearchWorker) { Name = term }; + childSearchThread.Start(new ChildSearchWorkerState{ Source = parentResults, Results = searchResults }); + } + + mSearches.Add(term, searchResults); + + return searchResults; + } + + private int GetCountOfAllDatabaseEntries() + { + return (from database in mDatabases select (int)database.RootGroup.GetEntriesCount(true)).Sum(); + } + + private void RootSearchWorker(object stateObject) + { + var results = (SearchResults)stateObject; + var excludeExpired = Settings.Default.ExcludeExpired; + var searchStartTime = DateTime.Now; + + foreach (var database in mDatabases) + { + SearchGroup(database, database.RootGroup, results, excludeExpired, searchStartTime); + } + + results.SetComplete(); + } + + /// + /// Recursively search and its children, adding results to + /// + private void SearchGroup(PwDatabase context, PwGroup group, SearchResults results, bool excludeExpired, DateTime searchStartTime) + { + if (group.EnableSearching ?? true) // Group will only be searched if it's parent enabled searching, so if it is inherit (null) or true, search it. + { + foreach (var childGroup in group.Groups) + { + SearchGroup(context, childGroup, results, excludeExpired, searchStartTime); + } + + foreach (var entry in group.Entries) + { + if (!(excludeExpired && entry.Expires && searchStartTime > entry.ExpiryTime)) + { + results.AddResultIfMatchesTerm(context, entry); + } + } + } + } + + private struct ChildSearchWorkerState + { + public SearchResults Source; + public SearchResults Results; + } + private void ChildSearchWorker(object stateObject) + { + var state = (ChildSearchWorkerState)stateObject; + + bool complete; + var index = 0; + do + { + foreach (var entry in state.Source.GetAvailableResults(ref index, out complete)) + { + state.Results.AddResultIfMatchesTerm(entry); + } + } while (!complete); + + state.Results.SetComplete(); + } + } +} diff --git a/AutoTypeSearch/Throbber.gif b/AutoTypeSearch/Throbber.gif new file mode 100755 index 0000000..494d426 --- /dev/null +++ b/AutoTypeSearch/Throbber.gif Binary files differ diff --git a/AutoTypeSearch/app.config b/AutoTypeSearch/app.config new file mode 100755 index 0000000..1370758 --- /dev/null +++ b/AutoTypeSearch/app.config @@ -0,0 +1,60 @@ + + + + +
+ + + + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + 0, 0, 0, 0 + + + True + + + False + + + True + + + False + + + False + + + PerformAutoType + + + EditEntry + + + None + + + + \ No newline at end of file diff --git a/CreatePlgX.bat b/CreatePlgX.bat new file mode 100755 index 0000000..59b9aa0 --- /dev/null +++ b/CreatePlgX.bat @@ -0,0 +1,20 @@ +@echo off +cd %~dp0 + +echo Deleting existing PlgX folder +rmdir /s /q PlgX + +echo Creating PlgX folder +mkdir PlgX + +echo Copying files +xcopy "AutoTypeSearch" PlgX /s /e /exclude:PlgXExclude.txt + +echo Compiling PlgX +"../KeePass/KeePass.exe" /plgx-create "%~dp0PlgX" --plgx-prereq-kp:2.27 + +echo Releasing PlgX +move /y PlgX.plgx "Releases\Build Outputs\AutoTypeSearch.plgx" + +echo Cleaning up +rmdir /s /q PlgX diff --git a/PlgXExclude.txt b/PlgXExclude.txt new file mode 100755 index 0000000..f626d58 --- /dev/null +++ b/PlgXExclude.txt @@ -0,0 +1,7 @@ +\bin\ +\obj\ +.user +.sln +.suo +.pdb +.xml \ No newline at end of file diff --git a/Readme.txt b/Readme.txt new file mode 100755 index 0000000..96d2fc0 --- /dev/null +++ b/Readme.txt @@ -0,0 +1,103 @@ +AutoTypeSearch +============== +http://sourceforge.net/projects/autotypesearch + + +This is a plugin to KeePass to provide a quick searching capability as +an enhancement to the global auto-type system. If a global auto-type is requested, but no matching +entry for the active window is found, this plugin will show a quick as-you-type search window which +lets you to easily pick the entry to auto-type. + + +Installation +------------ +Place AutoTypeSearch.plgx in your KeePass Plugins folder. + + +Usage +----- +AutoTypeSearch is initially configured to automatically appear after an unsuccessful global +auto-type. However, this can be changed in the KeePass Options window (an AutoTypeShow tab has +been added). Here, a system-wide hot key can be configured to show the AutoTypeSearch window +immediately. It is also possible to show the window by running KeePass.exe passing "/e1:AutoTypeSearch" +as a command line parameter. + +Once the window is shown, usage is extremely simple. Just start typing, and AutoTypeSearch will +search your database for matching entries. By default, the Title, Url, Notes, Tags, and Custom Fields +will be searched, but this can be configured in the AutoTypeShow tab of the KeePass Options window. + +Protected fields (like Password) will not be searched. + +The arrow keys can be used to move the selection in the list of results, then press Enter to auto- +type the selected entry. Alternatively, press Shift+Enter to open the entry instead of auto-typing it. +(These actions can also be customised in the Options window.) Clicking and Shift-Clicking an entry will +also perform those actions. + + +Uninstallation +-------------- +Delete AutoTypeSearch.plgx from your KeePass Plugins folder. + + +Checking for updates +-------------------- +If you want to use the KeePass Check for Updates function to check for updates to this plugin +then it requires the SourceForgeUpdateChecker plugin to be installed too: +http://sourceforge.net/projects/kpsfupdatechecker + + +Bug Reporting, Questions, Comments, Feedback, Donations +------------------------------------------------------- +Please use the SourceForge project page: +Bugs can be reported using the issue tracker, for anything else, a discussion forum is available. + + +Changelog +--------- +v0.1 + Initial release + +v0.2 + Added information banner when search is shown as a result of an unsuccessful global auto-type + Compatibility with Linux/Mono + +v0.3 + Added search result prioritisation for entries where the match is found at the start of the field + +v0.4 + Added support for multiple databases. All currently open, unlocked, databases will be searched + +v0.5 + Added support for KeePass 2.29 high resolution custom icons + +v0.6 + Where title does not uniquely identify the results shown, now also shows the group name as context + +v0.7 + Added support for the "Open entry URL" action. Use the Options window to choose this, if required. + +v0.8 + Added support for the "Copy password" action. Use the Options window to choose this, if required. + +v0.9 + Added workaround for mono bug under Linux that could cause an ArgumentOutOfRange crash when + searching if only a single result is initially returned. + +v0.91 + Fixed bug where up or down keys would cause an exception if there are no results to scroll through + +v0.10 + Compatibility with KeePass 2.41 (No longer compatible with previous versions) + +v0.11 + Diacritic (accent) insensitive searching + +v0.12 + Removed ugly white top border under Windows 10 + +v1.0 + Compatibility with KeePass 2.42. For versions of KeePass prior to 2.42, use an 0.X version. + +Attributions +------------ +Throbber image by FlipDarius http://www.mediawiki.org/wiki/File:Loading.gif \ No newline at end of file diff --git a/Releases/PackageRelease.bat b/Releases/PackageRelease.bat new file mode 100755 index 0000000..3fb646d --- /dev/null +++ b/Releases/PackageRelease.bat @@ -0,0 +1,23 @@ +@echo off +set version=1.0 +set output=%~dp0v%version%\ +set zipfile="%output%AutoTypeSearch-v%version%.zip" +set buildoutputs="%~dp0Build Outputs" + +rd /s /q "%output%" + +copy "%~dp0..\Readme.txt" %buildoutputs% +rem copy "%~dp0..\COPYING" %buildoutputs% + +rem don't include pdb files +rem del %buildoutputs%\*.pdb + +pushd %buildoutputs% +"%ProgramFiles%\7-Zip\7z.exe" a -tzip -mx9 -bd %zipfile% * +popd +copy "%~dp0..\Readme.txt" "%output%" + +set version= +set output= +set zipfile= +set buildoutputs= \ No newline at end of file