LaggKing
LaggKing

Reputation: 202

Global hotkeys, allows user to hold down modifier keys

I'm using a modified version of the code given here: https://stackoverflow.com/a/3654821/3179989

If the user presses Ctrl+B the hotkey activates.

If the user presses Ctrl+B, it actives, but continues to hold Ctrl then pressed B again, it does active.

Is there a way I can have the hotkey behave more like copy/paste does in windows?

For example, holding down Ctrl and tapping V will paste multiple times.

EDIT:

The issue was due to my addition of SendKeys.Send() when a hotkey was pushed. The original code above does not contain this problem. The question now is how can I send keys without losing this functionality?

Upvotes: 0

Views: 1212

Answers (1)

user1274820
user1274820

Reputation: 8144

EDIT:

To answer your new question :)

If you write your own global hook, you can specify these types of situations.

I believe the issue you're having is that if you send CTRL+V, it sends a KeyDown and KeyUp for Control, which makes the hotkey program assume that you are no longer holding it down. You need to explicitly handle this scenario by not changing your toggle during key sends.

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (!SENDING_KEYS) //If we're sending keys, ignore everything below
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) //KeyDown
            {
                int vkCode = Marshal.ReadInt32(lParam);
                string theKey = ((Keys)vkCode).ToString();
                Console.Write(theKey);
                if (theKey.Contains("ControlKey"))
                {
                    //Our Program still thinks CTRL is down even if we send it using SendKeys
                    CONTROL_DOWN = true; 
                }
                else if (CONTROL_DOWN && theKey == "B")
                {
                    Console.WriteLine("\n***HOTKEY PRESSED***"); //Our hotkey has been pressed
                    SENDING_KEYS = true; //Now we will be sending keys
                    SendKeys.Send("^v"); //Send the keys (CTRL+V) - Paste
                    SENDING_KEYS = false; //Now we are done sending the keys
                    return (IntPtr)1; //Block our hotkey from being sent anywhere
                }
                else if (theKey == "Escape")
                {
                    UnhookWindowsHookEx(_hookID);
                    Environment.Exit(0);
                }
            }
            else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP) //KeyUP
            {
                int vkCode = Marshal.ReadInt32(lParam);
                string theKey = ((Keys)vkCode).ToString();
                if (theKey.Contains("ControlKey"))
                {
                    //During send keys, this will not be triggered
                    CONTROL_DOWN = false; 
                }
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

Original Answer:

You can create your own Global Keyhook.

Here's an example using windows forms:

enter image description here

Here is an example in console:

enter image description here

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ConsoleKeyhook
{
    class Hooky
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        private static extern IntPtr GetModuleHandle(string lpModuleName);
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;
        private static bool CONTROL_DOWN = false;

        public static void Main()
        {
            _hookID = SetHook(_proc);
            Application.Run();
        }

        private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) //KeyDown
            {
                int vkCode = Marshal.ReadInt32(lParam);
                string theKey = ((Keys)vkCode).ToString();
                Console.Write(theKey);
                if (theKey.Contains("ControlKey"))
                {
                    CONTROL_DOWN = true;
                }
                else if (CONTROL_DOWN && theKey == "B")
                {
                    Console.WriteLine("\n***HOTKEY PRESSED***");
                }
                else if (theKey == "Escape")
                {
                    UnhookWindowsHookEx(_hookID);
                    Environment.Exit(0);
                }
            }
            else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP) //KeyUP
            {
                int vkCode = Marshal.ReadInt32(lParam);
                string theKey = ((Keys)vkCode).ToString();
                if (theKey.Contains("ControlKey"))
                {
                    CONTROL_DOWN = false;
                }
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }
}

Upvotes: 2

Related Questions