Devin Martin avatar Devin Martin committed 48a6f35

Move the KeeOtp project out of the root

Comments (0)

Files changed (31)

AsyncOperation.cs

-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-
-namespace KeeOtp
-{
-    /// <summary>
-    /// A helper class to help you with async operations since we aren't using tasks to avoid any reliance on the TPL
-    /// </summary>
-    /// <remarks>
-    /// This is a crude replacement for tasks in the .net 4.0 TPL.  In order to make this plugin work on machines
-    /// that don't have .net 4.0 we are avoiding TPL usage.
-    /// </remarks>
-    class AsyncOperation
-    {
-        readonly SynchronizationContext context;
-        readonly Action asyncAction;
-        readonly Action continuation;
-        readonly Action<Exception> catchAction;
-        readonly Action finallyAction;
-
-        readonly object postSync = new object();
-
-        bool catchPosted = false;
-        bool finallyPosted = false;
-
-        /// <summary>
-        /// Create an instance of Async Operation.
-        /// </summary>
-        /// <remarks>
-        /// This must be done on the UI thread if continuations and error handling are to also occur on the UI thread.
-        /// </remarks>
-        /// <param name="asyncAction">The action to run in the background</param>
-        /// <param name="continuation">The continuation to run agains the current synchronization context</param>
-        /// <param name="catchAction">Error handling to run agains the current synchronization context</param>
-        /// <param name="finallyAction">Finally to run agains the current synchronization context</param>
-        public AsyncOperation(Action asyncAction, Action continuation, Action<Exception> catchAction, Action finallyAction)
-        {
-            context = SynchronizationContext.Current;
-
-            this.asyncAction = asyncAction;
-            this.continuation = continuation;
-            this.catchAction = catchAction;
-            this.finallyAction = finallyAction;
-        }
-
-        /// <summary>
-        /// Run the async action delegate provided upon construction and post any continuations to the synchronization context that was avilable at construction
-        /// </summary>
-        public void Run()
-        {
-            Action runAction = () =>
-            {
-                try
-                {
-                    if (asyncAction != null)
-                        asyncAction();
-                    if (continuation != null)
-                    {
-                        this.PostDelegateToSynchronizationContext(() =>
-                        {
-                            try
-                            {
-                                continuation();
-                            }
-                            catch (Exception e)
-                            {
-                                PostCatchActionIfNeeded(e);
-                            }
-                            finally
-                            {
-                                PostFinallyActionIfNeeded();
-                            }
-                        });
-                    }
-                }
-                catch (Exception e)
-                {
-                    PostCatchActionIfNeeded(e);
-                }
-                finally
-                {
-                    PostFinallyActionIfNeeded();
-                }
-            };
-
-            ThreadPool.QueueUserWorkItem(state => runAction());
-        }
-
-        private void PostFinallyActionIfNeeded()
-        {
-            if (finallyAction != null)
-            {
-                lock (postSync)
-                {
-                    if (!finallyPosted)
-                    {
-                        finallyPosted = true;
-                        PostDelegateToSynchronizationContext(finallyAction);
-                    }
-                }
-            }
-        }
-
-        private void PostCatchActionIfNeeded(Exception e)
-        {
-            if (catchAction != null)
-            {
-                lock (postSync)
-                {
-                    if (!catchPosted)
-                    {
-                        catchPosted = true;
-                        PostDelegateToSynchronizationContext(() => catchAction(e));
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// If a synchronization context was available when the object was constructed, post the delegate.  Otherwise queue it up on the thread pool.
-        /// </summary>
-        /// <param name="action">Delegate to queue</param>
-        private void PostDelegateToSynchronizationContext(Action action)
-        {
-            if (action != null)
-            {
-                if (context != null)
-                    context.Post(state => action(), null);
-                else
-                    ThreadPool.QueueUserWorkItem(state => action());
-            }
-        }
-    }
-}

KeeOtp.csproj

-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.50727</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{4C1BB6F8-D2CD-49C2-9053-21705681356C}</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>KeeOtp</RootNamespace>
-    <AssemblyName>KeeOtp</AssemblyName>
-    <SignAssembly>true</SignAssembly>
-    <AssemblyOriginatorKeyFile>
-    </AssemblyOriginatorKeyFile>
-    <FileUpgradeFlags>
-    </FileUpgradeFlags>
-    <OldToolsVersion>4.0</OldToolsVersion>
-    <UpgradeBackupLocation>
-    </UpgradeBackupLocation>
-    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
-    <PublishUrl>publish\</PublishUrl>
-    <Install>true</Install>
-    <InstallFrom>Disk</InstallFrom>
-    <UpdateEnabled>false</UpdateEnabled>
-    <UpdateMode>Foreground</UpdateMode>
-    <UpdateInterval>7</UpdateInterval>
-    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
-    <UpdatePeriodically>false</UpdatePeriodically>
-    <UpdateRequired>false</UpdateRequired>
-    <MapFileExtensions>true</MapFileExtensions>
-    <ApplicationRevision>0</ApplicationRevision>
-    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
-    <IsWebBootstrapper>false</IsWebBootstrapper>
-    <UseApplicationTrust>false</UseApplicationTrust>
-    <BootstrapperEnabled>true</BootstrapperEnabled>
-    <TargetFrameworkProfile />
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="KeePass" />
-    <Reference Include="System" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Drawing" />
-    <Reference Include="System.Windows.Forms" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="AsyncOperation.cs" />
-    <Compile Include="OtpInformation.cs">
-      <SubType>Form</SubType>
-    </Compile>
-    <Compile Include="OtpInformation.Designer.cs">
-      <DependentUpon>OtpInformation.cs</DependentUpon>
-    </Compile>
-    <Compile Include="OtpType.cs" />
-    <Compile Include="KeeOtpExt.cs" />
-    <Compile Include="OtpAuthData.cs" />
-    <Compile Include="ShowOneTimePasswords.cs">
-      <SubType>Form</SubType>
-    </Compile>
-    <Compile Include="ShowOneTimePasswords.Designer.cs">
-      <DependentUpon>ShowOneTimePasswords.cs</DependentUpon>
-    </Compile>
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="Troubleshooting.cs">
-      <SubType>Form</SubType>
-    </Compile>
-    <Compile Include="Troubleshooting.Designer.cs">
-      <DependentUpon>Troubleshooting.cs</DependentUpon>
-    </Compile>
-  </ItemGroup>
-  <ItemGroup>
-    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
-      <Visible>False</Visible>
-      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
-      <Install>false</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
-      <Visible>False</Visible>
-      <ProductName>.NET Framework 3.5 SP1</ProductName>
-      <Install>true</Install>
-    </BootstrapperPackage>
-    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
-      <Visible>False</Visible>
-      <ProductName>Windows Installer 3.1</ProductName>
-      <Install>true</Install>
-    </BootstrapperPackage>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="OtpInformation.resx">
-      <DependentUpon>OtpInformation.cs</DependentUpon>
-    </EmbeddedResource>
-    <EmbeddedResource Include="ShowOneTimePasswords.resx">
-      <DependentUpon>ShowOneTimePasswords.cs</DependentUpon>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Troubleshooting.resx">
-      <DependentUpon>Troubleshooting.cs</DependentUpon>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="OtpSharp\OtpSharp\OtpSharp.csproj">
-      <Project>{df432b25-4ebe-4c89-b7bd-0553cb6301b9}</Project>
-      <Name>OtpSharp</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
-       Other similar extension points exist, see Microsoft.Common.targets.
-  <Target Name="BeforeBuild">
-  </Target>
-  <Target Name="AfterBuild">
-  </Target>
-  -->
-</Project>
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeeOtp", "KeeOtp.csproj", "{4C1BB6F8-D2CD-49C2-9053-21705681356C}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtpSharp", "OtpSharp\OtpSharp\OtpSharp.csproj", "{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtpSharp", "OtpSharp\OtpSharp\OtpSharp.csproj", "{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeeOtp", "KeeOtp\KeeOtp.csproj", "{4C1BB6F8-D2CD-49C2-9053-21705681356C}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Release|Any CPU = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Debug|Any CPU.ActiveCfg = Release35Client|Any CPU
+		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Debug|Any CPU.Build.0 = Release35Client|Any CPU
+		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Release|Any CPU.ActiveCfg = Release35Client|Any CPU
+		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Release|Any CPU.Build.0 = Release35Client|Any CPU
 		{4C1BB6F8-D2CD-49C2-9053-21705681356C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{4C1BB6F8-D2CD-49C2-9053-21705681356C}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{4C1BB6F8-D2CD-49C2-9053-21705681356C}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{4C1BB6F8-D2CD-49C2-9053-21705681356C}.Release|Any CPU.Build.0 = Release|Any CPU
-		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Debug|Any CPU.ActiveCfg = Release35Client|Any CPU
-		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Debug|Any CPU.Build.0 = Release35Client|Any CPU
-		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Release|Any CPU.ActiveCfg = Release35Client|Any CPU
-		{DF432B25-4EBE-4C89-B7BD-0553CB6301B9}.Release|Any CPU.Build.0 = Release35Client|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

KeeOtp/AsyncOperation.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace KeeOtp
+{
+    /// <summary>
+    /// A helper class to help you with async operations since we aren't using tasks to avoid any reliance on the TPL
+    /// </summary>
+    /// <remarks>
+    /// This is a crude replacement for tasks in the .net 4.0 TPL.  In order to make this plugin work on machines
+    /// that don't have .net 4.0 we are avoiding TPL usage.
+    /// </remarks>
+    class AsyncOperation
+    {
+        readonly SynchronizationContext context;
+        readonly Action asyncAction;
+        readonly Action continuation;
+        readonly Action<Exception> catchAction;
+        readonly Action finallyAction;
+
+        readonly object postSync = new object();
+
+        bool catchPosted = false;
+        bool finallyPosted = false;
+
+        /// <summary>
+        /// Create an instance of Async Operation.
+        /// </summary>
+        /// <remarks>
+        /// This must be done on the UI thread if continuations and error handling are to also occur on the UI thread.
+        /// </remarks>
+        /// <param name="asyncAction">The action to run in the background</param>
+        /// <param name="continuation">The continuation to run agains the current synchronization context</param>
+        /// <param name="catchAction">Error handling to run agains the current synchronization context</param>
+        /// <param name="finallyAction">Finally to run agains the current synchronization context</param>
+        public AsyncOperation(Action asyncAction, Action continuation, Action<Exception> catchAction, Action finallyAction)
+        {
+            context = SynchronizationContext.Current;
+
+            this.asyncAction = asyncAction;
+            this.continuation = continuation;
+            this.catchAction = catchAction;
+            this.finallyAction = finallyAction;
+        }
+
+        /// <summary>
+        /// Run the async action delegate provided upon construction and post any continuations to the synchronization context that was avilable at construction
+        /// </summary>
+        public void Run()
+        {
+            Action runAction = () =>
+            {
+                try
+                {
+                    if (asyncAction != null)
+                        asyncAction();
+                    if (continuation != null)
+                    {
+                        this.PostDelegateToSynchronizationContext(() =>
+                        {
+                            try
+                            {
+                                continuation();
+                            }
+                            catch (Exception e)
+                            {
+                                PostCatchActionIfNeeded(e);
+                            }
+                            finally
+                            {
+                                PostFinallyActionIfNeeded();
+                            }
+                        });
+                    }
+                }
+                catch (Exception e)
+                {
+                    PostCatchActionIfNeeded(e);
+                }
+                finally
+                {
+                    PostFinallyActionIfNeeded();
+                }
+            };
+
+            ThreadPool.QueueUserWorkItem(state => runAction());
+        }
+
+        private void PostFinallyActionIfNeeded()
+        {
+            if (finallyAction != null)
+            {
+                lock (postSync)
+                {
+                    if (!finallyPosted)
+                    {
+                        finallyPosted = true;
+                        PostDelegateToSynchronizationContext(finallyAction);
+                    }
+                }
+            }
+        }
+
+        private void PostCatchActionIfNeeded(Exception e)
+        {
+            if (catchAction != null)
+            {
+                lock (postSync)
+                {
+                    if (!catchPosted)
+                    {
+                        catchPosted = true;
+                        PostDelegateToSynchronizationContext(() => catchAction(e));
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// If a synchronization context was available when the object was constructed, post the delegate.  Otherwise queue it up on the thread pool.
+        /// </summary>
+        /// <param name="action">Delegate to queue</param>
+        private void PostDelegateToSynchronizationContext(Action action)
+        {
+            if (action != null)
+            {
+                if (context != null)
+                    context.Post(state => action(), null);
+                else
+                    ThreadPool.QueueUserWorkItem(state => action());
+            }
+        }
+    }
+}

KeeOtp/KeeOtp.csproj

+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.50727</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{4C1BB6F8-D2CD-49C2-9053-21705681356C}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>KeeOtp</RootNamespace>
+    <AssemblyName>KeeOtp</AssemblyName>
+    <SignAssembly>true</SignAssembly>
+    <AssemblyOriginatorKeyFile>
+    </AssemblyOriginatorKeyFile>
+    <FileUpgradeFlags>
+    </FileUpgradeFlags>
+    <OldToolsVersion>4.0</OldToolsVersion>
+    <UpgradeBackupLocation>
+    </UpgradeBackupLocation>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="KeePass" />
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="AsyncOperation.cs" />
+    <Compile Include="OtpInformation.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="OtpInformation.Designer.cs">
+      <DependentUpon>OtpInformation.cs</DependentUpon>
+    </Compile>
+    <Compile Include="OtpType.cs" />
+    <Compile Include="KeeOtpExt.cs" />
+    <Compile Include="OtpAuthData.cs" />
+    <Compile Include="ShowOneTimePasswords.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="ShowOneTimePasswords.Designer.cs">
+      <DependentUpon>ShowOneTimePasswords.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Troubleshooting.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Troubleshooting.Designer.cs">
+      <DependentUpon>Troubleshooting.cs</DependentUpon>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+      <Visible>False</Visible>
+      <ProductName>Windows Installer 3.1</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="OtpInformation.resx">
+      <DependentUpon>OtpInformation.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="ShowOneTimePasswords.resx">
+      <DependentUpon>ShowOneTimePasswords.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Troubleshooting.resx">
+      <DependentUpon>Troubleshooting.cs</DependentUpon>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\OtpSharp\OtpSharp\OtpSharp.csproj">
+      <Project>{df432b25-4ebe-4c89-b7bd-0553cb6301b9}</Project>
+      <Name>OtpSharp</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

KeeOtp/KeeOtpExt.cs

+using KeePass.Plugins;
+using KeePass.Util;
+using KeePass.Util.Spr;
+using KeePassLib;
+using KeePassLib.Utility;
+using OtpSharp;
+using System;
+using System.Windows.Forms;
+
+namespace KeeOtp
+{
+    public sealed class KeeOtpExt : Plugin
+    {
+        private IPluginHost host = null;
+        private ToolStripItem otpSeperatorToolStripItem;
+        private ToolStripItem otpDialogToolStripItem;
+        private ToolStripItem otpCopyToolStripItem;
+
+        private ToolStripItem otpTopDialogToolStripItem;
+        private ToolStripItem otpTopSeperatorToolStripItem;
+
+        public override bool Initialize(IPluginHost host)
+        {
+            if (host == null)
+                return false;
+            this.host = host;
+
+            this.otpSeperatorToolStripItem = new ToolStripSeparator();
+            host.MainWindow.EntryContextMenu.Items.Add(this.otpSeperatorToolStripItem);
+
+            this.otpDialogToolStripItem = host.MainWindow.EntryContextMenu.Items.Add("Timed One Time Password");
+            this.otpDialogToolStripItem.Click += new EventHandler(otpDialogToolStripItem_Click);
+
+            this.otpCopyToolStripItem = host.MainWindow.EntryContextMenu.Items.Add("Copy TOTP to Clipboard");
+            this.otpCopyToolStripItem.Click += otpCopyToolStripItem_Click;
+
+            SprEngine.FilterCompile += new EventHandler<SprEventArgs>(SprEngine_FilterCompile);
+
+            this.otpTopSeperatorToolStripItem = new ToolStripSeparator();
+            host.MainWindow.ToolsMenu.DropDownItems.Add(this.otpTopSeperatorToolStripItem);
+            this.otpTopDialogToolStripItem = host.MainWindow.ToolsMenu.DropDownItems.Add("Timed One Time Password",
+                null,
+                otpDialogToolStripItem_Click);
+
+            return true; // Initialization successful
+        }
+
+        void SprEngine_FilterCompile(object sender, SprEventArgs e)
+        {
+            if ((e.Context.Flags & SprCompileFlags.ExtActive) == SprCompileFlags.ExtActive)
+            {
+                if (e.Text.IndexOf("{TOTP}", StringComparison.InvariantCultureIgnoreCase) >= 0)
+                {
+                    if (e.Context.Entry.Strings.Exists(OtpAuthData.StringDictionaryKey))
+                    {
+                        var data = OtpAuthData.FromString(e.Context.Entry.Strings.Get(OtpAuthData.StringDictionaryKey).ReadString());
+                        var totp = new Totp(data.Key, step: data.Step, totpSize: data.Size);
+                        var text = totp.ComputeTotp().ToString().PadLeft(data.Size, '0');
+
+                        e.Text = StrUtil.ReplaceCaseInsensitive(e.Text, "{TOTP}", text);
+                    }
+                }
+            }
+        }
+
+        public override void Terminate()
+        {
+            // Remove all of our menu items
+            ToolStripItemCollection menu = host.MainWindow.EntryContextMenu.Items;
+            menu.Remove(otpSeperatorToolStripItem);
+            menu.Remove(otpDialogToolStripItem);
+            menu.Remove(otpCopyToolStripItem);
+
+            var toolsMenu = host.MainWindow.ToolsMenu;
+            toolsMenu.DropDownItems.Remove(this.otpTopSeperatorToolStripItem);
+            toolsMenu.DropDownItems.Remove(this.otpTopDialogToolStripItem);
+        }
+
+        void otpDialogToolStripItem_Click(object sender, EventArgs e)
+        {
+            PwEntry entry;
+            if (GetSelectedSingleEntry(out entry))
+            {
+                ShowOneTimePasswords form = new ShowOneTimePasswords(entry, host);
+                form.ShowDialog();
+            }
+        }
+
+        void otpCopyToolStripItem_Click(object sender, EventArgs e)
+        {
+            PwEntry entry;
+            if (this.GetSelectedSingleEntry(out entry))
+            {
+                if (!entry.Strings.Exists(OtpAuthData.StringDictionaryKey))
+                {
+                    if (MessageBox.Show("Must configure TOTP on this entry.  Do you want to do this now?", "Not Configured", MessageBoxButtons.YesNo) == DialogResult.Yes)
+                    {
+                        ShowOneTimePasswords form = new ShowOneTimePasswords(entry, host);
+                        form.ShowDialog();
+                    }
+                }
+                else
+                {
+                    var data = OtpAuthData.FromString(entry.Strings.Get(OtpAuthData.StringDictionaryKey).ReadString());
+                    var totp = new Totp(data.Key, step: data.Step, totpSize: data.Size);
+                    var text = totp.ComputeTotp().ToString().PadLeft(data.Size, '0');
+
+                    if (ClipboardUtil.CopyAndMinimize(new KeePassLib.Security.ProtectedString(true, text), true, this.host.MainWindow, entry, this.host.Database))
+                        this.host.MainWindow.StartClipboardCountdown();
+                }
+            }
+        }
+
+        private bool GetSelectedSingleEntry(out PwEntry entry)
+        {
+            entry = null;
+
+            var entries = this.host.MainWindow.GetSelectedEntries();
+            if (entries == null || entries.Length == 0)
+            {
+                MessageBox.Show("Please select an entry");
+                return false;
+            }
+            else if (entries.Length > 1)
+            {
+                MessageBox.Show("Please select only one entry");
+                return false;
+            }
+            else
+            {
+                // grab the entry that we care about
+                entry = entries[0];
+                return true;
+            }
+        }
+
+        public override string UpdateUrl
+        {
+            get { return "https://s3.amazonaws.com/KeeOtp/version_manifest.txt"; }
+        }
+    }
+}

KeeOtp/OtpAuthData.cs

+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Text;
+using System.Web;
+using OtpSharp;
+
+namespace KeeOtp
+{
+    /// <summary>
+    /// Class that serializes and deserializes data into the Strings for the entry
+    /// </summary>
+    public class OtpAuthData
+    {
+        public const string StringDictionaryKey = "otp";
+
+        const string keyParameter = "key";
+        const string typeParameter = "type";
+        const string stepParameter = "step";
+        const string sizeParameter = "size";
+        const string counterParameter = "counter";
+
+        public Key Key { get; set; }
+        public OtpType Type { get; set; }
+
+        public int Step { get; set; }
+        public int Size { get; set; }
+
+        public int Counter { get; set; }
+
+
+        public static OtpAuthData FromString(string data)
+        {
+            NameValueCollection parameters = ParseQueryString(data);
+
+            if (parameters[keyParameter] == null)
+                throw new ArgumentException("Must have a key in the data");
+
+            var otpData = new OtpAuthData();
+
+            otpData.Key = ProtectedKey.CreateProtectedKeyAndDestroyPlaintextKey(Base32.Decode(parameters[keyParameter]));
+
+            if (parameters[typeParameter] != null)
+                otpData.Type = (OtpType)Enum.Parse(typeof(OtpType), parameters[typeParameter]);
+
+            if (otpData.Type == OtpType.Totp)
+                otpData.Step = GetIntOrDefault(parameters, stepParameter, 30);
+            else if (otpData.Type == OtpType.Hotp)
+                otpData.Counter = GetIntOrDefault(parameters, counterParameter, 0);
+
+            otpData.Size = GetIntOrDefault(parameters, sizeParameter, 6);
+
+            return otpData;
+        }
+
+        /// <remarks>
+        /// Hacky query string parsing.  This was done due to reports
+        /// of people with just a 3.5 or 4.0 client profile getting errors
+        /// as the System.Web assembly where .net's implementation of
+        /// Url encoding and query string parsing is locate.
+        /// 
+        /// This should be fine since the only thing stored in the string
+        /// that needs to be encoded or decoded is the '=' sign.
+        /// </remarks>
+        private static NameValueCollection ParseQueryString(string data)
+        {
+            var collection = new NameValueCollection();
+
+            var parameters = data.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
+            foreach (var parameter in parameters)
+            {
+                if (parameter.Contains("="))
+                {
+                    var pieces = parameter.Split('=');
+                    if (pieces.Length != 2)
+                        continue;
+
+                    collection.Add(pieces[0], pieces[1].Replace("%3d", "="));
+                }
+            }
+
+            return collection;
+        }
+
+        private static int GetIntOrDefault(NameValueCollection parameters, string parameterKey, int defaultValue)
+        {
+            if (parameters[parameterKey] != null)
+            {
+                int step;
+                if (int.TryParse(parameters[parameterKey], out step))
+                    return step;
+                else
+                    return defaultValue;
+            }
+            else
+                return defaultValue;
+        }
+
+        /// <remarks>
+        /// Hacky query string parsing.  This was done due to reports
+        /// of people with just a 3.5 or 4.0 client profile getting errors
+        /// as the System.Web assembly where .net's implementation of
+        /// Url encoding and query string parsing is locate.
+        /// 
+        /// This should be fine since the only thing stored in the string
+        /// that needs to be encoded or decoded is the '=' sign.
+        /// </remarks>
+        public string EncodedString
+        {
+            get
+            {
+                NameValueCollection collection = new NameValueCollection();
+                string base32Key = null;
+                this.Key.UsePlainKey(key =>
+                {
+                    base32Key = Base32.Encode(key).Replace("=", "%3d");
+                });
+                collection.Add(keyParameter, base32Key);
+
+                if (this.Type != KeeOtp.OtpType.Totp)
+                    collection.Add(typeParameter, this.Type.ToString());
+
+                if (this.Type == KeeOtp.OtpType.Hotp)
+                    collection.Add(counterParameter, this.Counter.ToString());
+
+                else if (this.Type == KeeOtp.OtpType.Totp)
+                {
+                    if (this.Step != 30)
+                        collection.Add(stepParameter, this.Step.ToString());
+                }
+
+                if (this.Size != 6)
+                    collection.Add(sizeParameter, this.Size.ToString());
+
+                string data = string.Empty;
+                foreach (var key in collection.AllKeys)
+                {
+                    data += string.Format("{0}={1}&", key, collection[key]);
+                }
+
+                return data.TrimEnd('&');
+            }
+        }
+    }
+}

KeeOtp/OtpInformation.Designer.cs

+namespace KeeOtp
+{
+    partial class OtpInformation
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.textBoxKey = new System.Windows.Forms.TextBox();
+            this.textBoxStep = new System.Windows.Forms.TextBox();
+            this.radioButtonSix = new System.Windows.Forms.RadioButton();
+            this.labelKey = new System.Windows.Forms.Label();
+            this.label2 = new System.Windows.Forms.Label();
+            this.labelSize = new System.Windows.Forms.Label();
+            this.radioButtonEight = new System.Windows.Forms.RadioButton();
+            this.buttonOk = new System.Windows.Forms.Button();
+            this.buttonCancel = new System.Windows.Forms.Button();
+            this.radioButtonBase32 = new System.Windows.Forms.RadioButton();
+            this.radioButtonBase64 = new System.Windows.Forms.RadioButton();
+            this.radioButtonHex = new System.Windows.Forms.RadioButton();
+            this.SuspendLayout();
+            // 
+            // textBoxKey
+            // 
+            this.textBoxKey.Location = new System.Drawing.Point(31, 31);
+            this.textBoxKey.Name = "textBoxKey";
+            this.textBoxKey.Size = new System.Drawing.Size(231, 20);
+            this.textBoxKey.TabIndex = 0;
+            this.textBoxKey.TextChanged += new System.EventHandler(this.textBoxKey_TextChanged);
+            // 
+            // textBoxStep
+            // 
+            this.textBoxStep.Location = new System.Drawing.Point(140, 138);
+            this.textBoxStep.Name = "textBoxStep";
+            this.textBoxStep.Size = new System.Drawing.Size(23, 20);
+            this.textBoxStep.TabIndex = 1;
+            // 
+            // radioButtonSix
+            // 
+            this.radioButtonSix.AutoSize = true;
+            this.radioButtonSix.Checked = true;
+            this.radioButtonSix.Location = new System.Drawing.Point(140, 174);
+            this.radioButtonSix.Name = "radioButtonSix";
+            this.radioButtonSix.Size = new System.Drawing.Size(60, 17);
+            this.radioButtonSix.TabIndex = 2;
+            this.radioButtonSix.TabStop = true;
+            this.radioButtonSix.Text = "6 Digits";
+            this.radioButtonSix.UseVisualStyleBackColor = true;
+            // 
+            // labelKey
+            // 
+            this.labelKey.AutoSize = true;
+            this.labelKey.Location = new System.Drawing.Point(28, 15);
+            this.labelKey.Name = "labelKey";
+            this.labelKey.Size = new System.Drawing.Size(25, 13);
+            this.labelKey.TabIndex = 3;
+            this.labelKey.Text = "Key";
+            // 
+            // label2
+            // 
+            this.label2.AutoSize = true;
+            this.label2.Location = new System.Drawing.Point(28, 141);
+            this.label2.Name = "label2";
+            this.label2.Size = new System.Drawing.Size(106, 13);
+            this.label2.TabIndex = 4;
+            this.label2.Text = "Time Step (Seconds)";
+            // 
+            // labelSize
+            // 
+            this.labelSize.AutoSize = true;
+            this.labelSize.Location = new System.Drawing.Point(79, 174);
+            this.labelSize.Name = "labelSize";
+            this.labelSize.Size = new System.Drawing.Size(55, 13);
+            this.labelSize.TabIndex = 5;
+            this.labelSize.Text = "Code Size";
+            // 
+            // radioButtonEight
+            // 
+            this.radioButtonEight.AutoSize = true;
+            this.radioButtonEight.Location = new System.Drawing.Point(140, 197);
+            this.radioButtonEight.Name = "radioButtonEight";
+            this.radioButtonEight.Size = new System.Drawing.Size(60, 17);
+            this.radioButtonEight.TabIndex = 6;
+            this.radioButtonEight.Text = "8 Digits";
+            this.radioButtonEight.UseVisualStyleBackColor = true;
+            // 
+            // buttonOk
+            // 
+            this.buttonOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK;
+            this.buttonOk.Location = new System.Drawing.Point(106, 236);
+            this.buttonOk.Name = "buttonOk";
+            this.buttonOk.Size = new System.Drawing.Size(75, 23);
+            this.buttonOk.TabIndex = 7;
+            this.buttonOk.Text = "OK";
+            this.buttonOk.UseVisualStyleBackColor = true;
+            // 
+            // buttonCancel
+            // 
+            this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.buttonCancel.Location = new System.Drawing.Point(187, 236);
+            this.buttonCancel.Name = "buttonCancel";
+            this.buttonCancel.Size = new System.Drawing.Size(75, 23);
+            this.buttonCancel.TabIndex = 8;
+            this.buttonCancel.Text = "Cancel";
+            this.buttonCancel.UseVisualStyleBackColor = true;
+            // 
+            // radioButtonBase32
+            // 
+            this.radioButtonBase32.AutoSize = true;
+            this.radioButtonBase32.Location = new System.Drawing.Point(31, 57);
+            this.radioButtonBase32.Name = "radioButtonBase32";
+            this.radioButtonBase32.Size = new System.Drawing.Size(64, 17);
+            this.radioButtonBase32.TabIndex = 9;
+            this.radioButtonBase32.TabStop = true;
+            this.radioButtonBase32.Text = "Base 32";
+            this.radioButtonBase32.UseVisualStyleBackColor = true;
+            // 
+            // radioButtonBase64
+            // 
+            this.radioButtonBase64.AutoSize = true;
+            this.radioButtonBase64.Location = new System.Drawing.Point(31, 80);
+            this.radioButtonBase64.Name = "radioButtonBase64";
+            this.radioButtonBase64.Size = new System.Drawing.Size(64, 17);
+            this.radioButtonBase64.TabIndex = 10;
+            this.radioButtonBase64.TabStop = true;
+            this.radioButtonBase64.Text = "Base 64";
+            this.radioButtonBase64.UseVisualStyleBackColor = true;
+            // 
+            // radioButtonHex
+            // 
+            this.radioButtonHex.AutoSize = true;
+            this.radioButtonHex.Location = new System.Drawing.Point(31, 103);
+            this.radioButtonHex.Name = "radioButtonHex";
+            this.radioButtonHex.Size = new System.Drawing.Size(44, 17);
+            this.radioButtonHex.TabIndex = 11;
+            this.radioButtonHex.TabStop = true;
+            this.radioButtonHex.Text = "Hex";
+            this.radioButtonHex.UseVisualStyleBackColor = true;
+            // 
+            // OtpInformation
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(274, 271);
+            this.Controls.Add(this.radioButtonHex);
+            this.Controls.Add(this.radioButtonBase64);
+            this.Controls.Add(this.radioButtonBase32);
+            this.Controls.Add(this.buttonCancel);
+            this.Controls.Add(this.buttonOk);
+            this.Controls.Add(this.radioButtonEight);
+            this.Controls.Add(this.labelSize);
+            this.Controls.Add(this.label2);
+            this.Controls.Add(this.labelKey);
+            this.Controls.Add(this.radioButtonSix);
+            this.Controls.Add(this.textBoxStep);
+            this.Controls.Add(this.textBoxKey);
+            this.Name = "OtpInformation";
+            this.Text = "OtpInformation";
+            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OtpInformation_FormClosing);
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.TextBox textBoxKey;
+        private System.Windows.Forms.TextBox textBoxStep;
+        private System.Windows.Forms.RadioButton radioButtonSix;
+        private System.Windows.Forms.Label labelKey;
+        private System.Windows.Forms.Label label2;
+        private System.Windows.Forms.Label labelSize;
+        private System.Windows.Forms.RadioButton radioButtonEight;
+        private System.Windows.Forms.Button buttonOk;
+        private System.Windows.Forms.Button buttonCancel;
+        private System.Windows.Forms.RadioButton radioButtonBase32;
+        private System.Windows.Forms.RadioButton radioButtonBase64;
+        private System.Windows.Forms.RadioButton radioButtonHex;
+    }
+}

KeeOtp/OtpInformation.cs

+using System;
+using System.Windows.Forms;
+using OtpSharp;
+
+namespace KeeOtp
+{
+    public partial class OtpInformation : Form
+    {
+        const string validKeyChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+        public OtpAuthData Data { get; set; }
+
+        public OtpInformation()
+        {
+            InitializeComponent();
+            this.Shown += (sender, e) => FormWasShown();
+        }
+
+        private void FormWasShown()
+        {
+            if (this.Data != null)
+            {
+                this.Data.Key.UsePlainKey(key =>this.textBoxKey.Text = Base32.Encode(key));
+                this.textBoxStep.Text = this.Data.Step.ToString();
+
+                if (this.Data.Size == 8)
+                {
+                    this.radioButtonSix.Checked = false;
+                    this.radioButtonEight.Checked = true;
+                }
+                else
+                {
+                    this.radioButtonSix.Checked = true;
+                    this.radioButtonEight.Checked = false;
+                }
+            }
+            else
+                this.textBoxStep.Text = "30";
+        }
+
+        private void OtpInformation_FormClosing(object sender, FormClosingEventArgs e)
+        {
+            if (this.DialogResult == System.Windows.Forms.DialogResult.Cancel)
+                return;
+            try
+            {
+                if (string.IsNullOrEmpty(this.textBoxKey.Text))
+                {
+                    MessageBox.Show("A key must be set");
+                    e.Cancel = true;
+                    return;
+                }
+
+                if (this.textBoxKey.Text.Length < 8)
+                {
+                    MessageBox.Show("Key must be at least 8 characters.  If you are provided with less data then pad it up to 8 characters with '='s");
+                    e.Cancel = true;
+                    return;
+                }
+
+                var size = (this.radioButtonEight.Checked) ? 8 : 6;
+                int step;
+                
+                if (int.TryParse(this.textBoxStep.Text, out step))
+                {
+                    if (step != 30)
+                    {
+                        if (step <= 0)
+                        {
+                            this.textBoxStep.Text = "30";
+                            MessageBox.Show("The time step must be a non-zero positive integer.  The standard value is 30.  If you weren't specifically given an alternate value just use 30.");
+                            e.Cancel = true;
+                            return;
+                        }
+                        else if (MessageBox.Show("You have selected a non-standard time step.  30 is the standard recommended value.  You should only proceed if you were specifically told to use this time step size.  Do you wish to proceed?", "Non-standard time step size", MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.No)
+                        {
+                            e.Cancel = true;
+                            return;
+                        }
+                    }
+                }
+                else
+                {
+                    this.textBoxStep.Text = "30";
+                    MessageBox.Show("The time step must be a non-zero positive integer.  The standard value is 30.  If you weren't specifically given an alternate value just use 30.");
+                    e.Cancel = true;
+                    return;
+                }
+
+                var key = ProtectedKey.CreateProtectedKeyAndDestroyPlaintextKey(Base32.Decode(this.textBoxKey.Text));
+                this.Data = new OtpAuthData()
+                {
+                    Key = key,
+                    Size = size,
+                    Step = step
+                };
+            }
+            catch
+            {
+                e.Cancel = true;
+            }
+        }
+
+        private void textBoxKey_TextChanged(object sender, EventArgs e)
+        {
+            var unpaddedBase32 = this.textBoxKey.Text.ToUpperInvariant().TrimEnd('=');
+
+            bool invalid = false;
+            foreach (var c in unpaddedBase32)
+            {
+                if (validKeyChars.IndexOf(c) < 0)
+                {
+                    invalid = true;
+                    break;
+                }
+            }
+
+            if (invalid)
+            {
+                if (this.textBoxKey.Text.Length > 1)
+                    this.textBoxKey.Text = this.textBoxKey.Text.Substring(0, this.textBoxKey.Text.Length - 1);
+                else
+                    this.textBoxKey.Text = string.Empty;
+            }
+        }
+    }
+}

KeeOtp/OtpInformation.resx

+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

KeeOtp/OtpType.cs

+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace KeeOtp
+{
+    /// <summary>
+    /// The type of One Time Password to generate
+    /// </summary>
+    public enum OtpType
+    {
+        /// <summary>
+        /// Timed One Time Password
+        /// </summary>
+        Totp,
+        /// <summary>
+        /// HMAC One Time Password
+        /// </summary>
+        Hotp
+    }
+}

KeeOtp/Properties/AssemblyInfo.cs

+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General assembly properties
+[assembly: AssemblyTitle("KeeOTP")]
+[assembly: AssemblyDescription("A plugin that allows KeePass to calculate TOTP codes for use with services that support TOTP authentication.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Devin Martin")]
+[assembly: AssemblyProduct("KeePass Plugin")]
+[assembly: AssemblyCopyright("Copyright © 2012 Devin Martin")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// COM settings
+[assembly: ComVisible(false)]
+
+// Assembly GUID
+[assembly: Guid("9CCC719B-32BA-4936-A6D7-DBF0659C16EF")]
+
+// Assembly version information
+[assembly: AssemblyVersion("1.1.0")]
+[assembly: AssemblyFileVersion("1.1.0")]

KeeOtp/ShowOneTimePasswords.Designer.cs

+namespace KeeOtp
+{
+    partial class ShowOneTimePasswords
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            this.timerUpdateTotp = new System.Windows.Forms.Timer(this.components);
+            this.labelOtp = new System.Windows.Forms.Label();
+            this.labelRemaining = new System.Windows.Forms.Label();
+            this.labelRemainingLabel = new System.Windows.Forms.Label();
+            this.labelInstructions = new System.Windows.Forms.Label();
+            this.buttonClose = new System.Windows.Forms.Button();
+            this.buttonEdit = new System.Windows.Forms.Button();
+            this.buttonIncorrect = new System.Windows.Forms.Button();
+            this.SuspendLayout();
+            // 
+            // labelOtp
+            // 
+            this.labelOtp.AutoSize = true;
+            this.labelOtp.Font = new System.Drawing.Font("Microsoft Sans Serif", 48F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.labelOtp.Location = new System.Drawing.Point(24, 36);
+            this.labelOtp.Name = "labelOtp";
+            this.labelOtp.Size = new System.Drawing.Size(224, 73);
+            this.labelOtp.TabIndex = 0;
+            this.labelOtp.Text = "xxxxxx";
+            // 
+            // labelRemaining
+            // 
+            this.labelRemaining.AutoSize = true;
+            this.labelRemaining.Location = new System.Drawing.Point(145, 109);
+            this.labelRemaining.Name = "labelRemaining";
+            this.labelRemaining.Size = new System.Drawing.Size(12, 13);
+            this.labelRemaining.TabIndex = 1;
+            this.labelRemaining.Text = "x";
+            // 
+            // labelRemainingLabel
+            // 
+            this.labelRemainingLabel.AutoSize = true;
+            this.labelRemainingLabel.Location = new System.Drawing.Point(53, 109);
+            this.labelRemainingLabel.Name = "labelRemainingLabel";
+            this.labelRemainingLabel.Size = new System.Drawing.Size(86, 13);
+            this.labelRemainingLabel.TabIndex = 2;
+            this.labelRemainingLabel.Text = "Time Remaining:";
+            // 
+            // labelInstructions
+            // 
+            this.labelInstructions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.labelInstructions.Location = new System.Drawing.Point(12, 9);
+            this.labelInstructions.Name = "labelInstructions";
+            this.labelInstructions.Size = new System.Drawing.Size(304, 35);
+            this.labelInstructions.TabIndex = 3;
+            this.labelInstructions.Text = "Enter this code in the verification system.";
+            // 
+            // buttonClose
+            // 
+            this.buttonClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.buttonClose.DialogResult = System.Windows.Forms.DialogResult.OK;
+            this.buttonClose.Location = new System.Drawing.Point(241, 160);
+            this.buttonClose.Name = "buttonClose";
+            this.buttonClose.Size = new System.Drawing.Size(75, 23);
+            this.buttonClose.TabIndex = 4;
+            this.buttonClose.Text = "Close";
+            this.buttonClose.UseVisualStyleBackColor = true;
+            // 
+            // buttonEdit
+            // 
+            this.buttonEdit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.buttonEdit.Location = new System.Drawing.Point(160, 160);
+            this.buttonEdit.Name = "buttonEdit";
+            this.buttonEdit.Size = new System.Drawing.Size(75, 23);
+            this.buttonEdit.TabIndex = 5;
+            this.buttonEdit.Text = "Edit";
+            this.buttonEdit.UseVisualStyleBackColor = true;
+            this.buttonEdit.Click += new System.EventHandler(this.buttonEdit_Click);
+            // 
+            // buttonIncorrect
+            // 
+            this.buttonIncorrect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.buttonIncorrect.Location = new System.Drawing.Point(15, 160);
+            this.buttonIncorrect.Name = "buttonIncorrect";
+            this.buttonIncorrect.Size = new System.Drawing.Size(124, 23);
+            this.buttonIncorrect.TabIndex = 6;
+            this.buttonIncorrect.Text = "Not the correct code?";
+            this.buttonIncorrect.UseVisualStyleBackColor = true;
+            this.buttonIncorrect.Click += new System.EventHandler(this.buttonIncorrect_Click);
+            // 
+            // ShowOneTimePasswords
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(328, 198);
+            this.Controls.Add(this.buttonIncorrect);
+            this.Controls.Add(this.buttonEdit);
+            this.Controls.Add(this.buttonClose);
+            this.Controls.Add(this.labelInstructions);
+            this.Controls.Add(this.labelRemainingLabel);
+            this.Controls.Add(this.labelRemaining);
+            this.Controls.Add(this.labelOtp);
+            this.Name = "ShowOneTimePasswords";
+            this.Text = "Timed Passwords";
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Timer timerUpdateTotp;
+        private System.Windows.Forms.Label labelOtp;
+        private System.Windows.Forms.Label labelRemaining;
+        private System.Windows.Forms.Label labelRemainingLabel;
+        private System.Windows.Forms.Label labelInstructions;
+        private System.Windows.Forms.Button buttonClose;
+        private System.Windows.Forms.Button buttonEdit;
+        private System.Windows.Forms.Button buttonIncorrect;
+    }
+}

KeeOtp/ShowOneTimePasswords.cs

+using System;
+using System.Windows.Forms;
+using KeePass.Plugins;
+using KeePassLib.Security;
+using OtpSharp;
+
+namespace KeeOtp
+{
+    public partial class ShowOneTimePasswords : Form
+    {
+        private readonly KeePassLib.PwEntry entry;
+        private readonly IPluginHost host ;
+        private Totp totp;
+        private int lastCode;
+        private int lastRemainingTime;
+
+        OtpAuthData data;
+
+        public ShowOneTimePasswords(KeePassLib.PwEntry entry, IPluginHost host)
+        {
+            this.host = host;
+            this.entry = entry;
+            InitializeComponent();
+            this.Shown += (sender, e) => FormWasShown();
+            this.timerUpdateTotp.Tick += (sender, e) => UpdateDisplay();
+        }
+
+        private void UpdateDisplay()
+        {
+            var totp = this.totp;
+            if (totp != null)
+            {
+                var code = totp.ComputeTotp();
+                var remaining = totp.RemainingSeconds();
+
+                if (code != lastCode)
+                {
+                    lastCode = code;
+                    this.labelOtp.Text = code.ToString().PadLeft(this.data.Size, '0');
+                }
+                if (remaining != lastRemainingTime)
+                {
+                    lastRemainingTime = remaining;
+                    this.labelRemaining.Text = remaining.ToString();
+                }
+            }
+            else
+            {
+                MessageBox.Show("Please add a one time password field");
+                this.Close();
+            }
+        }
+
+        private void FormWasShown()
+        {
+            if (!entry.Strings.Exists(OtpAuthData.StringDictionaryKey))
+            {
+                this.AddEdit();
+            }
+            else
+            {
+                try
+                {
+                    this.data = OtpAuthData.FromString(entry.Strings.Get(OtpAuthData.StringDictionaryKey).ReadString());
+
+                    ShowCode();
+                }
+                catch
+                {
+                    this.AddEdit();
+                }
+            }
+        }
+
+        private void buttonEdit_Click(object sender, EventArgs e)
+        {
+            this.AddEdit();
+        }
+
+        private void ShowCode()
+        {
+            this.lastCode = 0;
+            this.lastRemainingTime = 0;
+
+            this.totp = new Totp(this.data.Key, step: this.data.Step, totpSize: this.data.Size);
+            this.timerUpdateTotp.Enabled = true;
+        }
+
+        private void AddEdit()
+        {
+            this.timerUpdateTotp.Enabled = false;
+            this.labelRemaining.Text = "x";
+            this.labelOtp.Text = "xxxxxx";
+            this.totp = null;
+
+            var addEditForm = new OtpInformation();
+            addEditForm.Data = this.data;
+
+            var result = addEditForm.ShowDialog();
+            if (result == System.Windows.Forms.DialogResult.OK)
+            {
+                this.data = addEditForm.Data;
+                // set the data
+                entry.Strings.Set(OtpAuthData.StringDictionaryKey, new ProtectedString(true, this.data.EncodedString));
+
+                // indicate that a change was made, must save
+                host.MainWindow.UpdateUI(false, null, true, host.Database.RootGroup, true, null, true);
+
+                this.ShowCode();
+            }
+            else if (this.data == null)
+                this.Close();
+            else
+                this.ShowCode();
+        }
+
+        private void buttonIncorrect_Click(object sender, EventArgs e)
+        {
+            Troubleshooting troubleshooting = new Troubleshooting();
+            troubleshooting.ShowDialog();
+        }
+    }
+}

KeeOtp/ShowOneTimePasswords.resx

+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="timerUpdateTotp.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>

KeeOtp/Troubleshooting.Designer.cs

+namespace KeeOtp
+{
+    partial class Troubleshooting
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.labelHeader = new System.Windows.Forms.Label();
+            this.buttonPingGoogle = new System.Windows.Forms.Button();
+            this.buttonTroubleshootingWebsite = new System.Windows.Forms.Button();
+            this.progressBarGettingTimeCorrection = new System.Windows.Forms.ProgressBar();
+            this.SuspendLayout();
+            // 
+            // labelHeader
+            // 
+            this.labelHeader.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.labelHeader.Location = new System.Drawing.Point(13, 13);
+            this.labelHeader.MaximumSize = new System.Drawing.Size(350, 48);
+            this.labelHeader.Name = "labelHeader";
+            this.labelHeader.Size = new System.Drawing.Size(341, 48);
+            this.labelHeader.TabIndex = 0;
+            this.labelHeader.Text = "There are many things that can cause an incorrect code.  We\'ll go through the mos" +
+    "t common things here.";
+            // 
+            // buttonPingGoogle
+            // 
+            this.buttonPingGoogle.Location = new System.Drawing.Point(16, 55);
+            this.buttonPingGoogle.Name = "buttonPingGoogle";
+            this.buttonPingGoogle.Size = new System.Drawing.Size(338, 23);
+            this.buttonPingGoogle.TabIndex = 1;
+            this.buttonPingGoogle.Text = "Ping Google for current time";
+            this.buttonPingGoogle.UseVisualStyleBackColor = true;
+            this.buttonPingGoogle.Click += new System.EventHandler(this.buttonPingGoogle_Click);
+            // 
+            // buttonTroubleshootingWebsite
+            // 
+            this.buttonTroubleshootingWebsite.Location = new System.Drawing.Point(16, 85);
+            this.buttonTroubleshootingWebsite.Name = "buttonTroubleshootingWebsite";
+            this.buttonTroubleshootingWebsite.Size = new System.Drawing.Size(338, 23);
+            this.buttonTroubleshootingWebsite.TabIndex = 2;
+            this.buttonTroubleshootingWebsite.Text = "Check the troubleshooting website";
+            this.buttonTroubleshootingWebsite.UseVisualStyleBackColor = true;
+            this.buttonTroubleshootingWebsite.Click += new System.EventHandler(this.buttonTroubleshootingWebsite_Click);
+            // 
+            // progressBarGettingTimeCorrection
+            // 
+            this.progressBarGettingTimeCorrection.Location = new System.Drawing.Point(16, 55);
+            this.progressBarGettingTimeCorrection.Name = "progressBarGettingTimeCorrection";
+            this.progressBarGettingTimeCorrection.Size = new System.Drawing.Size(338, 23);
+            this.progressBarGettingTimeCorrection.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+            this.progressBarGettingTimeCorrection.TabIndex = 3;
+            this.progressBarGettingTimeCorrection.Visible = false;
+            // 
+            // Troubleshooting
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(366, 129);
+            this.Controls.Add(this.progressBarGettingTimeCorrection);
+            this.Controls.Add(this.buttonTroubleshootingWebsite);
+            this.Controls.Add(this.buttonPingGoogle);
+            this.Controls.Add(this.labelHeader);
+            this.MinimumSize = new System.Drawing.Size(350, 48);
+            this.Name = "Troubleshooting";
+            this.Text = "Troubleshooting";
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Label labelHeader;
+        private System.Windows.Forms.Button buttonPingGoogle;
+        private System.Windows.Forms.Button buttonTroubleshootingWebsite;
+        private System.Windows.Forms.ProgressBar progressBarGettingTimeCorrection;
+    }
+}

KeeOtp/Troubleshooting.cs

+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Windows.Forms;
+using OtpSharp;
+
+namespace KeeOtp
+{
+    public partial class Troubleshooting : Form
+    {
+        public Troubleshooting()
+        {
+            InitializeComponent();
+        }
+
+        private void buttonPingGoogle_Click(object sender, EventArgs e)
+        {
+            // this is kind of an odd flow.
+
+            TimeCorrection timeCorrection = null;
+
+            // set up the asyn operation complete with continuation, catch and finally delegates
+            // the async operation will use the synchronization context to post the continuation,
+            // catch, and finally delegates to run on the UI thread.  Since we are creating this object
+            // on the UI thread it will use the UI synchronization context.
+            AsyncOperation getTimeCorrectionOperation = new AsyncOperation(() => // on background threadpool thread
+            {
+                timeCorrection = Ntp.GetTimeCorrectionFromGoogle();
+            },
+            () => // continuation on UI thread
+            {
+                var offset = timeCorrection.CorrectionFactor;
+
+                var totalSeconds = Math.Abs(offset.TotalSeconds);
+                if (totalSeconds == 0)
+                    MessageBox.Show("Your time is perfect according to Google's servers");
+                else if (totalSeconds <= 5)
+                    MessageBox.Show("Your time is off by five seconds or less from Google's servers.  You should be just fine.");
+                else if (totalSeconds <= 30)
+                    MessageBox.Show(string.Format("Your time is off by {0} seconds from Google's servers.  You are probably OK but correcting couldn't hurt.", totalSeconds));
+                else
+                    MessageBox.Show(string.Format("Your time is off by {0} seconds from Google's servers.  Try correcting the difference and try again.", totalSeconds));
+            },
+            ex => MessageBox.Show(ex.Message, "Error"), // catch on UI thread
+            () => // finally on UI thread
+            {
+                this.buttonPingGoogle.Visible = true;
+                this.progressBarGettingTimeCorrection.Visible = false;
+            });
+
+
+            this.buttonPingGoogle.Visible = false;
+            this.progressBarGettingTimeCorrection.Visible = true;
+
+            getTimeCorrectionOperation.Run();
+        }
+
+        private void buttonTroubleshootingWebsite_Click(object sender, EventArgs e)
+        {
+            // go to the troubleshooting page
+            var url = "https://bitbucket.org/devinmartin/keeotp/wiki/Troubleshooting";
+            Process ps = new Process();
+            ps.StartInfo = new ProcessStartInfo(url);
+            ps.Start();
+        }
+    }
+}

KeeOtp/Troubleshooting.resx

+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

KeeOtpExt.cs

-using KeePass.Plugins;
-using KeePass.Util;
-using KeePass.Util.Spr;
-using KeePassLib;
-using KeePassLib.Utility;
-using OtpSharp;
-using System;
-using System.Windows.Forms;
-
-namespace KeeOtp
-{
-    public sealed class KeeOtpExt : Plugin
-    {
-        private IPluginHost host = null;
-        private ToolStripItem otpSeperatorToolStripItem;
-        private ToolStripItem otpDialogToolStripItem;
-        private ToolStripItem otpCopyToolStripItem;
-
-        private ToolStripItem otpTopDialogToolStripItem;
-        private ToolStripItem otpTopSeperatorToolStripItem;
-
-        public override bool Initialize(IPluginHost host)
-        {
-            if (host == null)
-                return false;
-            this.host = host;
-
-            this.otpSeperatorToolStripItem = new ToolStripSeparator();
-            host.MainWindow.EntryContextMenu.Items.Add(this.otpSeperatorToolStripItem);
-
-            this.otpDialogToolStripItem = host.MainWindow.EntryContextMenu.Items.Add("Timed One Time Password");
-            this.otpDialogToolStripItem.Click += new EventHandler(otpDialogToolStripItem_Click);
-
-            this.otpCopyToolStripItem = host.MainWindow.EntryContextMenu.Items.Add("Copy TOTP to Clipboard");
-            this.otpCopyToolStripItem.Click += otpCopyToolStripItem_Click;
-
-            SprEngine.FilterCompile += new EventHandler<SprEventArgs>(SprEngine_FilterCompile);
-
-            this.otpTopSeperatorToolStripItem = new ToolStripSeparator();
-            host.MainWindow.ToolsMenu.DropDownItems.Add(this.otpTopSeperatorToolStripItem);
-            this.otpTopDialogToolStripItem = host.MainWindow.ToolsMenu.DropDownItems.Add("Timed One Time Password",
-                null,
-                otpDialogToolStripItem_Click);
-
-            return true; // Initialization successful
-        }
-
-        void SprEngine_FilterCompile(object sender, SprEventArgs e)
-        {
-            if ((e.Context.Flags & SprCompileFlags.ExtActive) == SprCompileFlags.ExtActive)
-            {
-                if (e.Text.IndexOf("{TOTP}", StringComparison.InvariantCultureIgnoreCase) >= 0)
-                {
-                    if (e.Context.Entry.Strings.Exists(OtpAuthData.StringDictionaryKey))
-                    {
-                        var data = OtpAuthData.FromString(e.Context.Entry.Strings.Get(OtpAuthData.StringDictionaryKey).ReadString());
-                        var totp = new Totp(data.Key, step: data.Step, totpSize: data.Size);
-                        var text = totp.ComputeTotp().ToString().PadLeft(data.Size, '0');
-
-                        e.Text = StrUtil.ReplaceCaseInsensitive(e.Text, "{TOTP}", text);
-                    }
-                }
-            }
-        }
-
-        public override void Terminate()
-        {
-            // Remove all of our menu items
-            ToolStripItemCollection menu = host.MainWindow.EntryContextMenu.Items;
-            menu.Remove(otpSeperatorToolStripItem);
-            menu.Remove(otpDialogToolStripItem);
-            menu.Remove(otpCopyToolStripItem);
-
-            var toolsMenu = host.MainWindow.ToolsMenu;
-            toolsMenu.DropDownItems.Remove(this.otpTopSeperatorToolStripItem);
-            toolsMenu.DropDownItems.Remove(this.otpTopDialogToolStripItem);
-        }
-