Global Hotkey in Console Application

Global hotkey in console application

What you can do is Create a hidden window in your Console application which is used to handle the hotkey notification and raise an event.

The code HERE demonstrates the principal. HERE is an article on handling messages in a Console application, using this you should be able to enhance HotKeyManager to run in a Console Application.

The following update to the HotKeyManager creates a background thread which runs the message loop and handles the windows messages.

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

namespace ConsoleHotKey
{
public static class HotKeyManager
{
public static event EventHandler<HotKeyEventArgs> HotKeyPressed;

public static int RegisterHotKey(Keys key, KeyModifiers modifiers)
{
_windowReadyEvent.WaitOne();
int id = System.Threading.Interlocked.Increment(ref _id);
_wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)modifiers, (uint)key);
return id;
}

public static void UnregisterHotKey(int id)
{
_wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
}

delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);

private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
{
RegisterHotKey(hwnd, id, modifiers, key);
}

private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
{
UnregisterHotKey(_hwnd, id);
}

private static void OnHotKeyPressed(HotKeyEventArgs e)
{
if (HotKeyManager.HotKeyPressed != null)
{
HotKeyManager.HotKeyPressed(null, e);
}
}

private static volatile MessageWindow _wnd;
private static volatile IntPtr _hwnd;
private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
static HotKeyManager()
{
Thread messageLoop = new Thread(delegate()
{
Application.Run(new MessageWindow());
});
messageLoop.Name = "MessageLoopThread";
messageLoop.IsBackground = true;
messageLoop.Start();
}

private class MessageWindow : Form
{
public MessageWindow()
{
_wnd = this;
_hwnd = this.Handle;
_windowReadyEvent.Set();
}

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY)
{
HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
HotKeyManager.OnHotKeyPressed(e);
}

base.WndProc(ref m);
}

protected override void SetVisibleCore(bool value)
{
// Ensure the window never becomes visible
base.SetVisibleCore(false);
}

private const int WM_HOTKEY = 0x312;
}

[DllImport("user32", SetLastError=true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

[DllImport("user32", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

private static int _id = 0;
}

public class HotKeyEventArgs : EventArgs
{
public readonly Keys Key;
public readonly KeyModifiers Modifiers;

public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
{
this.Key = key;
this.Modifiers = modifiers;
}

public HotKeyEventArgs(IntPtr hotKeyParam)
{
uint param = (uint)hotKeyParam.ToInt64();
Key = (Keys)((param & 0xffff0000) >> 16);
Modifiers = (KeyModifiers)(param & 0x0000ffff);
}
}

[Flags]
public enum KeyModifiers
{
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
NoRepeat = 0x4000
}
}

Here is an example of using HotKeyManager from a Console application

using System;
using System.Windows.Forms;

namespace ConsoleHotKey
{
class Program
{
static void Main(string[] args)
{
HotKeyManager.RegisterHotKey(Keys.A, KeyModifiers.Alt);
HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
Console.ReadLine();
}

static void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
{
Console.WriteLine("Hit me!");
}
}
}

C# global keyboard hook, that opens a form from a console application

What you need is a Lowlevel Keyboard Hook.

This could look somewhat like this:

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

public class LowLevelKeyboardHook
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_KEYUP = 0x101;
private const int WM_SYSKEYUP = 0x105;

[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);

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

public event EventHandler<Keys> OnKeyPressed;
public event EventHandler<Keys> OnKeyUnpressed;

private LowLevelKeyboardProc _proc;
private IntPtr _hookID = IntPtr.Zero;

public LowLevelKeyboardHook()
{
_proc = HookCallback;
}

public void HookKeyboard()
{
_hookID = SetHook(_proc);
}

public void UnHookKeyboard()
{
UnhookWindowsHookEx(_hookID);
}

private 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 IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);

OnKeyPressed.Invoke(this, ((Keys)vkCode));
}
else if(nCode >= 0 && wParam == (IntPtr)WM_KEYUP ||wParam == (IntPtr)WM_SYSKEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);

OnKeyUnpressed.Invoke(this, ((Keys)vkCode));
}

return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}

To implement it, you could use something like this:

kbh = new LowLevelKeyboardHook();
kbh.OnKeyPressed += kbh_OnKeyPressed;
kbh.OnKeyUnpressed += kbh_OnKeyUnpressed;
kbh.HookKeyboard();

The event could be handled like that:

bool lctrlKeyPressed;
bool f1KeyPressed;

void kbh_OnKeyPressed(object sender, Keys e)
{
if (e == Keys.LControlKey)
{
lctrlKeyPressed = true;
}
else if (e == Keys.F1)
{
f1KeyPressed= true;
}
CheckKeyCombo();
}

void kbh_OnKeyUnPressed(object sender, Keys e)
{
if (e == Keys.LControlKey)
{
lctrlKeyPressed = false;
}
else if (e == Keys.F1)
{
f1KeyPressed= false;
}
}

void CheckKeyCombo()
{
if (lctrlKeyPressed && f1KeyPressed)
{
//Open Form
}
}

For actual understanding, i would recommend you to have a read on P/Invoke. That is making use of the unmanaged APIs that windows provides.

For a full list of P/Invoke possibilites, pinvoke.net is a great source.

For better understanding in general, The official MSDN Website is a good source, too.

EDIT:

It seems like you're actually using a Console Application, not a WinForm one. In that case, you have to run the program a bit differently:

[STAThread]
static void Main()
{

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

LowLevelKeyboardHook kbh = new LowLevelKeyboardHook();
kbh.OnKeyPressed += kbh_OnKeyPressed;
kbh.OnKeyUnpressed += kbh_OnKeyUnpressed;
kbh.HookKeyboard();

Application.Run();

kbh.UnHookKeyboard();

}

The Run() method of the Application Class starts a standard loop for your application. This is necessary for the Hook to work, because a mere Console Application without this loop is, as far as I know, not capable of triggering those global key events.

Using this implementation, pressing and releasing the defined keys gives the following output:

Console Output

Note: I obviously replaced

Application.Run(new Form1());

in the CheckKeyCombo() method with

Console.WriteLine("KeyCombo pressed");

Multiple global hotkeys in C# console application

This will not work with the class you are using.

Only register one HotKeyPressed-Event. In this event you can check the HotKeyEventArgs with a simple if-statement to determine which of your hotkeys was pressed.

Global hotkeys, allows user to hold down modifier keys

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:

Sample Image

Here is an example in console:

Sample Image

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);
}
}
}

Set global hotkeys using C#

Please note that this code will not trigger events in console application projects. You have to use WinForms project for events to fire.

This is the correct code:

public sealed  class KeyboardHook : IDisposable
{
// Registers a hot key with Windows.
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
// Unregisters the hot key with Windows.
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

/// <summary>
/// Represents the window that is used internally to get the messages.
/// </summary>
private class Window : NativeWindow, IDisposable
{
private static int WM_HOTKEY = 0x0312;

public Window()
{
// create the handle for the window.
this.CreateHandle(new CreateParams());
}

/// <summary>
/// Overridden to get the notifications.
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

// check if we got a hot key pressed.
if (m.Msg == WM_HOTKEY)
{
// get the keys.
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);

// invoke the event to notify the parent.
if (KeyPressed != null)
KeyPressed(this, new KeyPressedEventArgs(modifier, key));
}
}

public event EventHandler<KeyPressedEventArgs> KeyPressed;

#region IDisposable Members

public void Dispose()
{
this.DestroyHandle();
}

#endregion
}

private Window _window = new Window();
private int _currentId;

public KeyboardHook()
{
// register the event of the inner native window.
_window.KeyPressed += delegate(object sender, KeyPressedEventArgs args)
{
if (KeyPressed != null)
KeyPressed(this, args);
};
}

/// <summary>
/// Registers a hot key in the system.
/// </summary>
/// <param name="modifier">The modifiers that are associated with the hot key.</param>
/// <param name="key">The key itself that is associated with the hot key.</param>
public void RegisterHotKey(ModifierKeys modifier, Keys key)
{
// increment the counter.
_currentId = _currentId + 1;

// register the hot key.
if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key))
throw new InvalidOperationException("Couldn’t register the hot key.");
}

/// <summary>
/// A hot key has been pressed.
/// </summary>
public event EventHandler<KeyPressedEventArgs> KeyPressed;

#region IDisposable Members

public void Dispose()
{
// unregister all the registered hot keys.
for (int i = _currentId; i > 0; i--)
{
UnregisterHotKey(_window.Handle, i);
}

// dispose the inner native window.
_window.Dispose();
}

#endregion
}

/// <summary>
/// Event Args for the event that is fired after the hot key has been pressed.
/// </summary>
public class KeyPressedEventArgs : EventArgs
{
private ModifierKeys _modifier;
private Keys _key;

internal KeyPressedEventArgs(ModifierKeys modifier, Keys key)
{
_modifier = modifier;
_key = key;
}

public ModifierKeys Modifier
{
get { return _modifier; }
}

public Keys Key
{
get { return _key; }
}
}

/// <summary>
/// The enumeration of possible modifiers.
/// </summary>
[Flags]
public enum ModifierKeys : uint
{
Alt = 1,
Control = 2,
Shift = 4,
Win = 8
}

to use (i had to edit the modifier keys to cast them (modifier)1 (modifier)2 etc

public partial  class Form1 : Form
{
KeyboardHook hook = new KeyboardHook();

public Form1()
{
InitializeComponent();

// register the event that is fired after the key press.
hook.KeyPressed +=
new EventHandler<KeyPressedEventArgs>(hook_KeyPressed);
// register the control + alt + F12 combination as hot key.
hook.RegisterHotKey(ModifierKeys.Control | ModifierKeys.Alt,
Keys.F12);
}

void hook_KeyPressed(object sender, KeyPressedEventArgs e)
{
// show the keys pressed in a label.
label1.Text = e.Modifier.ToString() + " + " + e.Key.ToString();
}
}

Deployment of C# console application for Keyboard Listener

Have you tried setting lParam to null? It's been a while since i've done key handling, so not sure if it's better to set to null or IntPtr.Zero. One of the two should work:

public static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
Keys pressedKey = (Keys)Marshal.ReadInt32(lParam);

if (pressedKey == Keys.F11 || pressedKey == Keys.F12)
{
// Do something...

// Don't pass the key press on to the system
lParam = null;
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}


Related Topics



Leave a reply



Submit