Author Topic: Physical keyboard & mouse input blocking (obsoleted by v1.7.1)  (Read 6233 times)

Exergist

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 405
  • Ride the lightning
VoiceAttack (VA) currently cannot easily block user input on a large scale. In other words, VA cannot currently block physical key presses, mouse movement, and mouse clicks. These kinds of physical input may disrupt virtual input sent by VA, causing undesirable results in your applications or games.

**DISCLAIMER**
Please use the following inline function and associated command with caution. I have done the best I can to thoroughly test the code (it appears to work as expected in Windows 7 and Windows 10), however it is shared "as is" without any warranty of any kind. It is your responsibility to read all the provided information and understand how to properly use the code.

The command "Keyboard & Mouse Input Block Toggle" blocks physical mouse and keyboard input while still allowing virtual input from VA to pass through to your applications. This is accomplished with low level mouse and keyboard hooks executed via a C# inline function. This command should be implemented in pairs - the first command instance will activate blocking and the second instance will deactivate blocking. Just put your other VA commands that you want shielded from physical input between the two input blocking command instances.

**IMPORTANT**
While blocking is enabled you will have no direct control over the keyboard and mouse. However, you can press the "Escape" key to immediately terminate input blocking. The "Escape" key press will still not be sent through to your applications but it will signal for the blocking to terminate and thus will restore your ability to provide physical keyboard and mouse input. You can change this termination key to whatever you like from within the inline function (see line 61). The state of the input blocking is outputted to the VA log.

Finally, you can uncomment code lines 66 and 67 in the inline function and specify exception physical keys that will be allowed to pass through to your applications. You can also uncomment code lines 96-98 and specify exception physical mouse buttons/movement that will be allowed to pass through to your applications.

For the purposes of full upfront disclosure you can find the entire contents of the attached profile below.

Block Input Example:
Code: [Select]
Write '[Blue] Begin test' to log

Activate input blocking (terminate by pressing "Escape" key)
Execute command, 'Keyboard & Mouse Input Block Toggle' (and wait until it completes)

Your input-shielded commands and actions go here
Run application 'notepad.exe' - wait until it starts
Quick Input, 'Hello World! Go ahead and try to interrupt this input! '
Press NumPad Enter key and hold for 0.03 seconds and release
Quick Input, 'Physical keyboard and mouse input is being blocked...'
Press Enter key and hold for 0.03 seconds and release
Quick Input, '...but virtual VoiceAttack input is allowed to pass through to your applications!'
Press Enter key and hold for 0.03 seconds and release
Quick Input, 'You can deactivate the input blocking by pressing the "Escape" key. '
Press NumPad Enter key and hold for 0.03 seconds and release
Quick Input, 'The VoiceAttack log will output blocking status information. '
Press Enter key and hold for 0.03 seconds and release

Deactivate input blocking
Execute command, 'Keyboard & Mouse Input Block Toggle' (and wait until it completes)

Write '[Blue] Test complete' to log

Keyboard & Mouse Input Block Toggle (Command):
Code: [Select]
Toggles blocking of physical mouse & keyboard input. Press the "Escape" key to manually deactivate blocking.
Inline C# Function: Keyboard & Mouse Input Block Toggle
Slight pause to ensure the keyboard and mouse blocking fully initializes before running further VoiceAttack commands
Pause 0.05 seconds

Keyboard & Mouse Input Block Toggle Function (Inline C# Function):
Code: [Select]
// Mouse and Keyboard Physical Input Blocker
// Blocks physical mouse and keyboard input while still allowing virtual input from VoiceAttack to pass through to your applications. This is accomplished with low level mouse and keyboard hooks.
// The command containing this inline function should be implemented in pairs: the first command instance will activate blocking and the second instance will deactivate blocking.
// Just put your VA commands that you want shielded from physical input between the two command instances.
// Input blocking can also be terminated by pressing the "Escape" key. This can be changed to whatever key you like (see code line 61).
// You can uncomment code lines 66 and 67 and specify exception physical keys that will be allowed to pass through to your applications.
// You can also uncomment code lines 96-98 and specify exception physical mouse buttons/movement that will be allowed to pass through to your applications.
// This code is shared "as is" without any warranty of any kind. It is your responsibility to read all the provided information and understand how to properly use the code.

// Sources & Credits:
// Peter Hinchley ==> https://hinchley.net/articles/creating-a-key-logger-via-a-global-system-hook-using-powershell/
// Stephen Toub ==> https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-mouse-hook-in-c/ and https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/

// VoiceAttack Referenced Assemblies: Microsoft.CSharp.dll;System.dll;System.Core.dll;System.Data.dll;System.Data.DataSetExtensions.dll;System.Deployment.dll;System.Drawing.dll;System.Net.Http.dll; System.Windows.Forms.dll;System.Xml.dll;System.Xml.Linq.dll

using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;

public class VAInline
{
    public void main()
    {
        if (VA.GetBoolean("MyInputBlockState").GetValueOrDefault(false) == true) // Evaluate VA variable "MyInputBlockState" (set to FALSE if variable is null)
        { // MyInputBlockState = true (blocking is currently active)
            VA.WriteToLog("Input blocking terminated automatically", "blue"); // Output blocking state information to user
            UnhookWindowsHookEx(KeyboardHook); // Unhook the keyboard
            UnhookWindowsHookEx(MouseHook); // Unhook the mouse
            VA.SetBoolean("MyInputBlockState", false); // Set VA variable "MyInputBlockState" to FALSE
            return;
        }
        else // MyInputBlockState = false (blocking is currently not active)
        {
            VA.SetBoolean("MyInputBlockState", true); // Set VA variable "MyInputBlockState" to TRUE
            KeyboardHook = SetKeyboardHook(KeyboardhookProc); // Activate the keyboard hook
            MouseHook = SetMouseHook(MousehookProc); // Activate the mouse hook
            VA.WriteToLog("Input blocking activated", "blue"); // Output blocking state information to user
            Application.Run(); // Begin running standard appliation message loop in current thread

            UnhookWindowsHookEx(KeyboardHook); // Unhook the keyboard
            UnhookWindowsHookEx(MouseHook); // Unhook the mouse
            VA.SetBoolean("MyInputBlockState", false); // Set VA variable "MyInputBlockState" to FALSE
            VA.WriteToLog("Input blocking terminated by user", "blue"); // Output blocking state information to user
            return;
        }
    }

    private static IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam) // Low level keyboard event processing
    {
        if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN | wParam == (IntPtr)WM_SYSKEYDOWN)) // If a specific keyboard event occurs then process it
        {
            KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
            KeysConverter kc = new KeysConverter();
            string KeyName = kc.ConvertToString((Keys)kbd.vkCode); // Store keyboard key name

            if (!kbd.flags.ToString().Contains("LLKHF_INJECTED")) // check if keyboard input event flags do not contain "LLKHF_INJECTED"
            {   // input was not "injected" (virtually sent), meaning the input was from the physical keyboard
                if (KeyName == "Escape") // check if physical keyboard event was an "Escape" key press
                {
                    Application.ExitThread(); // Terminate current thread. This will allow the code after main() code line "Application.Run()" to execute.
                    return (IntPtr)1; // Blocks other programs further downstream in the hook chain from seeing the physical keyboard event
                }
                // else if (KeyName == "End" | KeyName == "Home") // Specify name of exception key(s) that will be passed through (https://docs.microsoft.com/en-us/windows-hardware/customize/enterprise/keyboardfilter-key-names)
                //     return CallNextHookEx(KeyboardHook, nCode, wParam, lParam); // Send the keyboard event information to other downstream hooks
                else // All other keyboard key presses
                {
                    return (IntPtr)1; // Blocks other programs further downstream in the hook chain from seeing the physical keyboard event
                }
            }
        }

        return CallNextHookEx(KeyboardHook, nCode, wParam, lParam); // Send the keyboard event information to other downstream hooks
    }

    private static IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) // Low level mouse event processing
    {
        // If a specific mouse event occurs then process it
        if (nCode >= 0 && (wParam == (IntPtr)WM_MOUSEMOVE | // wParam == (IntPtr)WM_NCMOUSEMOVE | // Mouse movement events
        wParam == (IntPtr)WM_LBUTTONDOWN | wParam == (IntPtr)WM_LBUTTONUP | // Mouse LButton down/up events
        wParam == (IntPtr)WM_RBUTTONDOWN | wParam == (IntPtr)WM_RBUTTONUP | // Mouse RButton down/up events
        wParam == (IntPtr)WM_MBUTTONDOWN | wParam == (IntPtr)WM_MBUTTONUP | // Mouse MButton down/up events
        wParam == (IntPtr)WM_XBUTTONDOWN | wParam == (IntPtr)WM_XBUTTONUP | // Mouse XButton1 & XButton2 down/up events
        // wParam == (IntPtr)WM_NCLBUTTONDOWN | wParam == (IntPtr)WM_NCLBUTTONUP | // Non-client window area mouse LButton down/up events
        // wParam == (IntPtr)WM_NCRBUTTONDOWN | wParam == (IntPtr)WM_NCRBUTTONUP | // Non-client window area mouse RButton down/up events
        // wParam == (IntPtr)WM_NCMBUTTONDOWN | wParam == (IntPtr)WM_NCMBUTTONUP | // Non-client window area mouse MButton down/up events
        // wParam == (IntPtr)WM_NCXBUTTONDOWN | wParam == (IntPtr)WM_NCXBUTTONUP | // Non-client window area mouse XButton1 & XButton2 down/up events
        wParam == (IntPtr)WM_MOUSEWHEEL)) // Mouse wheel rotate event
        {       
            MSLLHOOKSTRUCT mouse = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));

            if (mouse.flags == 0) // Check if mouse input event flags are zero (LLMHF_INJECTED or LLMHF_LOWER_IL_INJECTED flags not thrown)
            {   // Mouse input was physically sent
                // if (wParam == (IntPtr)WM_RBUTTONDOWN) // Specify name of exception button(s) that will be passed through
                  //  return CallNextHookEx(KeyboardHook, nCode, wParam, lParam); // Send the mouse event information to other downstream hooks
                // else
                    return (IntPtr)1; // Blocks other programs further downstream in the hook chain from seeing the physical mouse event
            }
        }

        return CallNextHookEx(MouseHook, nCode, wParam, lParam); // ; Send the mouse event information to other downstream hooks
    }

    [DllImport("user32.dll")] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);
    [DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr myHook);
    [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr myHook, int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandle(string lpModuleName);

    // Keyboard and mouse hook designation
    private const int WH_KEYBOARD_LL = 13;
    private const int WH_MOUSE_LL = 14;

    // Keyboard input messages
    private const int WM_KEYDOWN = 0x0100;
    private const int WM_SYSKEYDOWN = 0x0104;

    // Mouse input messages (client area of window)
    private const int WM_MOUSEMOVE = 0x0200;
    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;
    private const int WM_RBUTTONDOWN = 0x0204;
    private const int WM_RBUTTONUP = 0x0205;
    private const int WM_MBUTTONDOWN = 0x0207;
    private const int WM_MBUTTONUP = 0x0208;
    private const int WM_XBUTTONDOWN = 0x020B;
    private const int WM_XBUTTONUP = 0x020C;
    private const int WM_MOUSEWHEEL = 0x020A;
    // Mouse input messages (non-client area of window)
    // private const int WM_NCMOUSEMOVE = 0x00A0;
    // private const int WM_NCLBUTTONDOWN = 0x00A1;
    // private const int WM_NCLBUTTONUP = 0x00A2;
    // private const int WM_NCRBUTTONDOWN = 0x00A4;
    // private const int WM_NCRBUTTONUP = 0x00A5;
    // private const int WM_NCMBUTTONDOWN = 0x00A7;
    // private const int WM_NCMBUTTONUP = 0x00A8;
    // private const int WM_NCXBUTTONDOWN = 0x00AB;
    // private const int WM_NCXBUTTONUP = 0x00AC;

    private static HookProc KeyboardhookProc = KeyboardHookCallback;
    private static HookProc MousehookProc = MouseHookCallback;
    private static IntPtr KeyboardHook = IntPtr.Zero;
    private static IntPtr MouseHook = IntPtr.Zero;

    [StructLayout(LayoutKind.Sequential)]
    public struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public KBDLLHOOKSTRUCTFlags flags;
        public uint time;
        public UIntPtr dwExtraInfo;
    }

    public struct POINT
    {
        public int x;
        public int y;
    }

    public struct MSLLHOOKSTRUCT
    {
        public POINT pt;
        public uint mouseData;
        public MSLLHOOKSTRUCTFlags flags;
        public uint time;
        public UIntPtr dwExtraInfo;
    }

    [Flags]
    public enum KBDLLHOOKSTRUCTFlags : uint
    {
        LLKHF_EXTENDED = 0x01,
        LLKHF_INJECTED = 0x10,
        LLKHF_ALTDOWN = 0x20,
        LLKHF_UP = 0x80,
    }

    public enum MSLLHOOKSTRUCTFlags : uint
    {
        LLMHF_INJECTED = 0x01,
        LLMHF_LOWER_IL_INJECTED = 0x02,
    }

    private static IntPtr SetKeyboardHook(HookProc KeyboardHookProc)
    {
        IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
        return SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, moduleHandle, 0);
    }

    private static IntPtr SetMouseHook(HookProc MouseHookProc)
    {
        IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
        return SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, moduleHandle, 0);
    }

    private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
}


UPDATE:
'Block Mouse Input' and 'Block Keyboard Input' actions were added to the core VA functionality as of v1.7.0.5. Based on my testing these new actions are far more intuitive to use and effectively obsolete the above inline function. Gary indicated that the new actions will 'attempt' to perform blocking, so if you run into a case where the new actions are not achieving your desired effect you can certainly try the above method.
« Last Edit: July 18, 2018, 01:43:54 PM by Exergist »