diff --git a/SuperPutty/Properties/AssemblyInfo.cs b/SuperPutty/Properties/AssemblyInfo.cs
index 2434e06f..a241231e 100644
--- a/SuperPutty/Properties/AssemblyInfo.cs
+++ b/SuperPutty/Properties/AssemblyInfo.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@@ -17,6 +18,7 @@
// 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)]
+[assembly: InternalsVisibleTo("SuperPuttyUnitTests")]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("7f061c0e-1ca2-43a8-92f5-49563ff4ee81")]
diff --git a/SuperPutty/SuperPutty.csproj b/SuperPutty/SuperPutty.csproj
index 9d20575b..49bf90b3 100644
--- a/SuperPutty/SuperPutty.csproj
+++ b/SuperPutty/SuperPutty.csproj
@@ -281,6 +281,7 @@
+
@@ -518,4 +519,4 @@ SET errorlevel=
-->
-
\ No newline at end of file
+
diff --git a/SuperPutty/Utils/KeyboardHookState.cs b/SuperPutty/Utils/KeyboardHookState.cs
new file mode 100644
index 00000000..be4255b6
--- /dev/null
+++ b/SuperPutty/Utils/KeyboardHookState.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Windows.Forms;
+
+namespace SuperPutty.Utils
+{
+ internal sealed class KeyboardHookState
+ {
+ internal static readonly UIntPtr SuperPuttyInputMarker = new UIntPtr(0x53505459u);
+
+ private bool isLeftControlDown;
+ private bool isRightControlDown;
+ private bool isLeftShiftDown;
+ private bool isRightShiftDown;
+ private bool isLeftAltDown;
+ private bool isRightAltDown;
+ private bool isLeftWinDown;
+ private bool isRightWinDown;
+
+ internal long Version { get; private set; }
+
+ internal bool IsControlDown { get { return isLeftControlDown || isRightControlDown; } }
+ internal bool IsShiftDown { get { return isLeftShiftDown || isRightShiftDown; } }
+ internal bool IsAltDown { get { return isLeftAltDown || isRightAltDown; } }
+ internal bool IsWinDown { get { return isLeftWinDown || isRightWinDown; } }
+
+ internal void Update(Keys keyCode, bool isKeyDown)
+ {
+ switch (keyCode)
+ {
+ case Keys.LControlKey:
+ SetState(ref isLeftControlDown, isKeyDown);
+ break;
+ case Keys.RControlKey:
+ SetState(ref isRightControlDown, isKeyDown);
+ break;
+ case Keys.LShiftKey:
+ SetState(ref isLeftShiftDown, isKeyDown);
+ break;
+ case Keys.RShiftKey:
+ SetState(ref isRightShiftDown, isKeyDown);
+ break;
+ case Keys.LMenu:
+ SetState(ref isLeftAltDown, isKeyDown);
+ break;
+ case Keys.RMenu:
+ SetState(ref isRightAltDown, isKeyDown);
+ break;
+ case Keys.LWin:
+ SetState(ref isLeftWinDown, isKeyDown);
+ break;
+ case Keys.RWin:
+ SetState(ref isRightWinDown, isKeyDown);
+ break;
+ }
+ }
+
+ internal void Synchronize(Func isKeyDown)
+ {
+ if (isKeyDown == null)
+ {
+ throw new ArgumentNullException("isKeyDown");
+ }
+
+ bool leftControlDown = isKeyDown(Keys.LControlKey);
+ bool rightControlDown = isKeyDown(Keys.RControlKey);
+ bool leftShiftDown = isKeyDown(Keys.LShiftKey);
+ bool rightShiftDown = isKeyDown(Keys.RShiftKey);
+ bool leftAltDown = isKeyDown(Keys.LMenu);
+ bool rightAltDown = isKeyDown(Keys.RMenu);
+ bool leftWinDown = isKeyDown(Keys.LWin);
+ bool rightWinDown = isKeyDown(Keys.RWin);
+
+ bool changed =
+ isLeftControlDown != leftControlDown ||
+ isRightControlDown != rightControlDown ||
+ isLeftShiftDown != leftShiftDown ||
+ isRightShiftDown != rightShiftDown ||
+ isLeftAltDown != leftAltDown ||
+ isRightAltDown != rightAltDown ||
+ isLeftWinDown != leftWinDown ||
+ isRightWinDown != rightWinDown;
+
+ isLeftControlDown = leftControlDown;
+ isRightControlDown = rightControlDown;
+ isLeftShiftDown = leftShiftDown;
+ isRightShiftDown = rightShiftDown;
+ isLeftAltDown = leftAltDown;
+ isRightAltDown = rightAltDown;
+ isLeftWinDown = leftWinDown;
+ isRightWinDown = rightWinDown;
+
+ if (changed)
+ {
+ Version++;
+ }
+ }
+
+ internal void Reset()
+ {
+ if (IsControlDown || IsShiftDown || IsAltDown || IsWinDown)
+ {
+ Version++;
+ }
+
+ isLeftControlDown = false;
+ isRightControlDown = false;
+ isLeftShiftDown = false;
+ isRightShiftDown = false;
+ isLeftAltDown = false;
+ isRightAltDown = false;
+ isLeftWinDown = false;
+ isRightWinDown = false;
+ }
+
+ internal static bool IsSuperPuttyInjectedInput(NativeMethods.KBDLLHOOKSTRUCT keyboardData)
+ {
+ return (keyboardData.flags & NativeMethods.LowLevelKeyboardFlags.Injected) != 0 &&
+ keyboardData.dwExtraInfo.Equals(SuperPuttyInputMarker);
+ }
+
+ private void SetState(ref bool currentState, bool newState)
+ {
+ if (currentState != newState)
+ {
+ currentState = newState;
+ Version++;
+ }
+ }
+ }
+}
diff --git a/SuperPutty/Utils/NativeMethods.cs b/SuperPutty/Utils/NativeMethods.cs
index 937929fb..318dfd75 100644
--- a/SuperPutty/Utils/NativeMethods.cs
+++ b/SuperPutty/Utils/NativeMethods.cs
@@ -68,6 +68,77 @@ public const uint
KEYEVENTF_EXTENDEDKEY = 0x0001,
KEYEVENTF_KEYUP = 0x0002;
+ public const uint INPUT_KEYBOARD = 1;
+
+ [Flags]
+ public enum LowLevelKeyboardFlags : uint
+ {
+ Extended = 0x01,
+ LowerIntegrityLevelInjected = 0x02,
+ Injected = 0x10,
+ AltDown = 0x20,
+ KeyUp = 0x80
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KBDLLHOOKSTRUCT
+ {
+ public uint vkCode;
+ public uint scanCode;
+ public LowLevelKeyboardFlags flags;
+ public uint time;
+ public UIntPtr dwExtraInfo;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct INPUT
+ {
+ public uint type;
+ public InputUnion data;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct InputUnion
+ {
+ [FieldOffset(0)]
+ public MOUSEINPUT mouse;
+
+ [FieldOffset(0)]
+ public KEYBDINPUT keyboard;
+
+ [FieldOffset(0)]
+ public HARDWAREINPUT hardware;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MOUSEINPUT
+ {
+ public int dx;
+ public int dy;
+ public uint mouseData;
+ public uint dwFlags;
+ public uint time;
+ public UIntPtr dwExtraInfo;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KEYBDINPUT
+ {
+ public ushort wVk;
+ public ushort wScan;
+ public uint dwFlags;
+ public uint time;
+ public UIntPtr dwExtraInfo;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct HARDWAREINPUT
+ {
+ public uint uMsg;
+ public ushort wParamL;
+ public ushort wParamH;
+ }
+
[Flags]
public enum AnimateWindowFlags
{
@@ -1102,9 +1173,6 @@ public static class HotKeysConstants
[DllImport("user32.dll")]
public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
- [DllImport("user32.dll")]
- public static extern short GetKeyState(int nVirtKey);
-
[DllImport("user32.dll")]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
@@ -1239,6 +1307,12 @@ public override string ToString()
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(uint idHook, LowLevelKMProc lpfn, IntPtr hMod, uint dwThreadId);
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
+
+ [DllImport("user32.dll")]
+ public static extern short GetAsyncKeyState(int vKey);
+
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
diff --git a/SuperPutty/frmSuperPutty.cs b/SuperPutty/frmSuperPutty.cs
index dbbe8152..ad3bf42c 100644
--- a/SuperPutty/frmSuperPutty.cs
+++ b/SuperPutty/frmSuperPutty.cs
@@ -71,6 +71,7 @@ public partial class frmSuperPutty : Form
private FormWindowState lastNonMinimizedWindowState = FormWindowState.Normal;
private Rectangle lastNormalDesktopBounds;
private ChildWindowFocusHelper focusHelper;
+ private readonly KeyboardHookState keyboardHookState = new KeyboardHookState();
int commandMRUIndex = -1;
private readonly TabSwitcher tabSwitcher;
@@ -161,6 +162,10 @@ public frmSuperPutty()
// Low-Level Mouse and Keyboard hooks
llkp = KBHookCallback;
kbHookID = SetKBHook(llkp);
+ if (kbHookID == IntPtr.Zero)
+ {
+ Log.ErrorFormat("Unable to install the keyboard hook. Win32Error={0}", Marshal.GetLastWin32Error());
+ }
//llmp = MHookCallback;
//mHookID = SetMHook(llmp);
@@ -272,7 +277,11 @@ private void frmSuperPutty_Load(object sender, EventArgs e)
protected override void OnFormClosed(FormClosedEventArgs e)
{
// free hooks
- NativeMethods.UnhookWindowsHookEx(kbHookID);
+ if (kbHookID != IntPtr.Zero && !NativeMethods.UnhookWindowsHookEx(kbHookID))
+ {
+ Log.WarnFormat("Unable to remove the keyboard hook. Win32Error={0}", Marshal.GetLastWin32Error());
+ }
+ kbHookID = IntPtr.Zero;
//NativeMethods.UnhookWindowsHookEx(mHookID);
// save window size and location if not maximized or minimized
@@ -352,9 +361,29 @@ public void FocusActiveDocument(string caller)
private void frmSuperPutty_Activated(object sender, EventArgs e)
{
Log.DebugFormat("[{0}] Activated", this.Handle);
+ keyboardHookState.Synchronize(IsAsyncKeyDown);
//dockPanel1_ActiveDocumentChanged(null, null);
}
+ protected override void OnDeactivate(EventArgs e)
+ {
+ long modifierStateVersion = keyboardHookState.Version;
+ base.OnDeactivate(e);
+
+ if (IsHandleCreated && !IsDisposed && !Disposing)
+ {
+ BeginInvoke(new Action(() =>
+ {
+ if (!IsDisposed && !Disposing &&
+ modifierStateVersion == keyboardHookState.Version &&
+ !IsForegroundWindow(true))
+ {
+ keyboardHookState.Reset();
+ }
+ }));
+ }
+ }
+
public void SetActiveDocument(ToolWindow toolWindow)
{
if (this.DockPanel.ActiveDocument != toolWindow)
@@ -1279,11 +1308,9 @@ private static IntPtr SetKBHook(NativeMethods.LowLevelKMProc proc)
}
}
- private IntPtr foregroundBeforeWinDown = IntPtr.Zero;
-
- private static bool GetKeyDown(Keys keyCode)
+ private static bool IsAsyncKeyDown(Keys keyCode)
{
- return NativeMethods.GetKeyState((int)keyCode) < 0;
+ return (NativeMethods.GetAsyncKeyState((int)keyCode) & 0x8000) != 0;
}
// Intercept keyboard messages for Ctrl-F4 and Ctrl-Tab handling
@@ -1291,24 +1318,37 @@ private IntPtr KBHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
- int vkCode = Marshal.ReadInt32(lParam);
- Keys keys = (Keys)vkCode;
+ NativeMethods.KBDLLHOOKSTRUCT keyboardData =
+ (NativeMethods.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.KBDLLHOOKSTRUCT));
+ Keys keys = (Keys)keyboardData.vkCode;
+
+ // Let input generated by SuperPuTTY pass through without treating it
+ // as physical state or recursively executing SuperPuTTY shortcuts.
+ if (KeyboardHookState.IsSuperPuttyInjectedInput(keyboardData))
+ {
+ return NativeMethods.CallNextHookEx(kbHookID, nCode, wParam, lParam);
+ }
- // get key state of control/alt/shift is up/down
bool isKeyDown = wParam == (IntPtr)NativeMethods.WM_KEYDOWN || wParam == (IntPtr)NativeMethods.WM_SYSKEYDOWN;
- bool isControlDown = GetKeyDown(Keys.LControlKey) || GetKeyDown(Keys.RControlKey);
- bool isShiftDown = GetKeyDown(Keys.LShiftKey) || GetKeyDown(Keys.RShiftKey);
- bool isAltDown = GetKeyDown(Keys.LMenu) || GetKeyDown(Keys.RMenu);
- bool isWinDown = GetKeyDown(Keys.LWin) || GetKeyDown(Keys.RWin);
+ keyboardHookState.Update(keys, isKeyDown);
+
+ bool isControlDown = keyboardHookState.IsControlDown;
+ bool isShiftDown = keyboardHookState.IsShiftDown;
+ bool isAltDown = keyboardHookState.IsAltDown;
+ bool isWinDown = keyboardHookState.IsWinDown;
if (Log.Logger.IsEnabledFor(Level.Trace))
{
Log.DebugFormat("### KBHook: nCode={0}, wParam={1}, lParam={2} ({4,-4} - {3}) [{5}{6}{7}{8}]",
- nCode, wParam, vkCode, keys, isKeyDown ? "Down" : "Up",
- isControlDown ? "Ctrl" : "", isAltDown ? "Alt" : "", isAltDown ? "Shift" : "", isWinDown ? "Win" : "");
+ nCode, wParam, keyboardData.vkCode, keys, isKeyDown ? "Down" : "Up",
+ isControlDown ? "Ctrl" : "", isAltDown ? "Alt" : "", isShiftDown ? "Shift" : "", isWinDown ? "Win" : "");
}
- if (IsForegroundWindow(true))
+ IntPtr foregroundWindow = NativeMethods.GetForegroundWindow();
+ bool embeddedWindowIsForeground = IsForegroundWindow(foregroundWindow, false);
+ bool superPuttyIsForeground = embeddedWindowIsForeground || foregroundWindow == Handle;
+
+ if (superPuttyIsForeground)
{
// SuperPutty or Putty is the window in front...
@@ -1386,43 +1426,53 @@ private IntPtr KBHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
}
}
- if (IsForegroundWindow(false))
+ if (embeddedWindowIsForeground)
{
if (isKeyDown && isWinDown && (keys & Keys.KeyCode) == Keys.Left)
{
- if ((keys & Keys.Shift) == Keys.Shift)
+ if (isShiftDown)
{
ShiftWindow(-1);
}
else
{
- SnapWindow(Keys.Left);
+ if (!TrySnapWindow(Keys.Left))
+ {
+ return NativeMethods.CallNextHookEx(kbHookID, nCode, wParam, lParam);
+ }
}
return (IntPtr)1;
}
if (isKeyDown && isWinDown && (keys & Keys.KeyCode) == Keys.Right)
{
- if ((keys & Keys.Shift) == Keys.Shift)
+ if (isShiftDown)
{
ShiftWindow(1);
}
else
{
- SnapWindow(Keys.Right);
+ if (!TrySnapWindow(Keys.Right))
+ {
+ return NativeMethods.CallNextHookEx(kbHookID, nCode, wParam, lParam);
+ }
}
return (IntPtr)1;
}
if (isKeyDown && isWinDown && (keys & Keys.KeyCode) == Keys.Up)
{
- SnapWindow(Keys.Up);
- return (IntPtr)1;
+ if (TrySnapWindow(Keys.Up))
+ {
+ return (IntPtr)1;
+ }
}
if (isKeyDown && isWinDown && (keys & Keys.KeyCode) == Keys.Down)
{
- SnapWindow(Keys.Down);
- return (IntPtr)1;
+ if (TrySnapWindow(Keys.Down))
+ {
+ return (IntPtr)1;
+ }
}
- if (isKeyDown && (keys & Keys.Modifiers) == Keys.Alt && (keys & Keys.KeyCode) == Keys.F4)
+ if (isKeyDown && isAltDown && (keys & Keys.KeyCode) == Keys.F4)
{
Application.Exit();
return (IntPtr)1;
@@ -1471,7 +1521,11 @@ private bool FindChildControl(Control control, IntPtr hWnd)
private bool IsForegroundWindow(bool includeMainForm)
{
- IntPtr fgWindow = NativeMethods.GetForegroundWindow();
+ return IsForegroundWindow(NativeMethods.GetForegroundWindow(), includeMainForm);
+ }
+
+ private bool IsForegroundWindow(IntPtr fgWindow, bool includeMainForm)
+ {
if (includeMainForm && this.Handle == fgWindow) return true; // main form is FG
if (FindChildControl(this.DockPanel, fgWindow))
@@ -1955,26 +2009,62 @@ private void OnIconFilterClicked(object sender, ImageListPopupEventArgs e)
#endregion
- private void SnapWindow(Keys direction)
+ private bool TrySnapWindow(Keys direction)
{
- NativeMethods.SetForegroundWindow(this.Handle);
+ IntPtr previousForegroundWindow = NativeMethods.GetForegroundWindow();
+ if (!NativeMethods.SetForegroundWindow(this.Handle))
+ {
+ Log.WarnFormat("Unable to focus SuperPuTTY before snapping {0}. Win32Error={1}",
+ direction, Marshal.GetLastWin32Error());
+ return false;
+ }
+
+ // The physical Windows key is still down. Forward only the arrow to
+ // the main form so Windows performs the snap without changing the
+ // user's physical Windows-key state.
+ NativeMethods.INPUT[] inputs =
+ {
+ CreateKeyboardInput(direction, NativeMethods.KEYEVENTF_EXTENDEDKEY),
+ CreateKeyboardInput(direction, NativeMethods.KEYEVENTF_EXTENDEDKEY | NativeMethods.KEYEVENTF_KEYUP)
+ };
+
+ uint sent = NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
+ if (sent != (uint)inputs.Length)
+ {
+ Log.WarnFormat("Unable to send snap input for {0}. Sent {1} of {2} events. Win32Error={3}",
+ direction, sent, inputs.Length, Marshal.GetLastWin32Error());
- NativeMethods.keybd_event((byte)Keys.LWin, 0, 0, 0);
- NativeMethods.keybd_event((byte)direction, 0, 0, 0);
- NativeMethods.keybd_event((byte)direction, 0, NativeMethods.KEYEVENTF_KEYUP, 0);
- NativeMethods.keybd_event((byte)Keys.LWin, 0, NativeMethods.KEYEVENTF_KEYUP, 0);
+ if (previousForegroundWindow != IntPtr.Zero && previousForegroundWindow != Handle &&
+ !NativeMethods.SetForegroundWindow(previousForegroundWindow))
+ {
+ Log.WarnFormat("Unable to restore the previous foreground window after snap failure. Win32Error={0}",
+ Marshal.GetLastWin32Error());
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ private static NativeMethods.INPUT CreateKeyboardInput(Keys key, uint flags)
+ {
+ return new NativeMethods.INPUT
+ {
+ type = NativeMethods.INPUT_KEYBOARD,
+ data = new NativeMethods.InputUnion
+ {
+ keyboard = new NativeMethods.KEYBDINPUT
+ {
+ wVk = (ushort)key,
+ dwFlags = flags,
+ dwExtraInfo = KeyboardHookState.SuperPuttyInputMarker
+ }
+ }
+ };
}
private void ShiftWindow(int offset)
{
-/* Keys direction = (offset < 0) ? Keys.Left : Keys.Right;
- NativeMethods.keybd_event((byte)Keys.LWin, 0, 0, 0);
- NativeMethods.keybd_event((byte)Keys.ShiftKey, 0, 0, 0);
- NativeMethods.keybd_event((byte)direction, 0, 0, 0);
- NativeMethods.keybd_event((byte)direction, 0, NativeMethods.KEYEVENTF_KEYUP, 0);
- NativeMethods.keybd_event((byte)Keys.ShiftKey, 0, NativeMethods.KEYEVENTF_KEYUP, 0);
- NativeMethods.keybd_event((byte)Keys.LWin, 0, NativeMethods.KEYEVENTF_KEYUP, 0);
-*/
if (Screen.AllScreens.Length < 2)
return;
diff --git a/SuperPuttyUnitTests/KeyboardHookStateTests.cs b/SuperPuttyUnitTests/KeyboardHookStateTests.cs
new file mode 100644
index 00000000..ee49ce0e
--- /dev/null
+++ b/SuperPuttyUnitTests/KeyboardHookStateTests.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using NUnit.Framework;
+using SuperPutty.Utils;
+
+namespace SuperPuttyUnitTests
+{
+ [TestFixture]
+ public class KeyboardHookStateTests
+ {
+ [Test]
+ public void TracksLeftAndRightModifiersIndependently()
+ {
+ KeyboardHookState state = new KeyboardHookState();
+
+ state.Update(Keys.LControlKey, true);
+ state.Update(Keys.RControlKey, true);
+ state.Update(Keys.LControlKey, false);
+
+ Assert.True(state.IsControlDown);
+
+ state.Update(Keys.RControlKey, false);
+
+ Assert.False(state.IsControlDown);
+ }
+
+ [Test]
+ public void ResetClearsAllModifiers()
+ {
+ KeyboardHookState state = new KeyboardHookState();
+ state.Update(Keys.LControlKey, true);
+ state.Update(Keys.RShiftKey, true);
+ state.Update(Keys.LMenu, true);
+ state.Update(Keys.RWin, true);
+
+ state.Reset();
+
+ Assert.False(state.IsControlDown);
+ Assert.False(state.IsShiftDown);
+ Assert.False(state.IsAltDown);
+ Assert.False(state.IsWinDown);
+ }
+
+ [Test]
+ public void SynchronizeRestoresPhysicallyHeldModifiers()
+ {
+ KeyboardHookState state = new KeyboardHookState();
+ HashSet downKeys = new HashSet { Keys.LShiftKey, Keys.RWin };
+
+ state.Synchronize(downKeys.Contains);
+
+ Assert.False(state.IsControlDown);
+ Assert.True(state.IsShiftDown);
+ Assert.False(state.IsAltDown);
+ Assert.True(state.IsWinDown);
+ }
+
+ [Test]
+ public void IdentifiesOnlyMarkedSuperPuttyInput()
+ {
+ NativeMethods.KBDLLHOOKSTRUCT keyboardData = new NativeMethods.KBDLLHOOKSTRUCT
+ {
+ flags = NativeMethods.LowLevelKeyboardFlags.Injected,
+ dwExtraInfo = KeyboardHookState.SuperPuttyInputMarker
+ };
+
+ Assert.True(KeyboardHookState.IsSuperPuttyInjectedInput(keyboardData));
+
+ keyboardData.dwExtraInfo = UIntPtr.Zero;
+ Assert.False(KeyboardHookState.IsSuperPuttyInjectedInput(keyboardData));
+
+ keyboardData.flags = 0;
+ keyboardData.dwExtraInfo = KeyboardHookState.SuperPuttyInputMarker;
+ Assert.False(KeyboardHookState.IsSuperPuttyInjectedInput(keyboardData));
+ }
+ }
+}
diff --git a/SuperPuttyUnitTests/SuperPuttyUnitTests.csproj b/SuperPuttyUnitTests/SuperPuttyUnitTests.csproj
index 623d2df3..e2bc9435 100644
--- a/SuperPuttyUnitTests/SuperPuttyUnitTests.csproj
+++ b/SuperPuttyUnitTests/SuperPuttyUnitTests.csproj
@@ -90,6 +90,7 @@
+
@@ -156,4 +157,4 @@
-->
-
\ No newline at end of file
+