I would like to intercept while I push CTRL + A + A
I read the article
How can I register a global hot key to say CTRL+SHIFT+(LETTER) using WPF and .NET 3.5?
and someone pasted a code originally posted here:
https://www.fluxbytes.com/csharp/how-to-register-a-global-hotkey-for-your-application-in-c/?unapproved=2279&moderation-hash=b3ec34d2621e0be051ed354f09c436d2#comment-2279
Anyway, I tried to change the code adding and "&" but this and some other attempt was wrong.
How can I get if I press CTRL + A + A ?
Thanks for the help!
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace GlobalHotkeyExampleForm
{
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
}
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
enum KeyModifier
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
WinKey = 8
}
public Form1()
{
InitializeComponent();
int id = 0; // The id of the hotkey.
RegisterHotKey(this.Handle, id, (int)KeyModifier.Control, Keys.A.GetHashCode()); // Register ctrl + a as global hotkey.
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0312)
{
/* Note that the three lines below are not needed if you only want to register one hotkey.
* The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF); // The key of the hotkey that was pressed.
KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF); // The modifier of the hotkey that was pressed.
int id = m.WParam.ToInt32(); // The id of the hotkey that was pressed.
MessageBox.Show("Hotkey has been pressed!");
// do something
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
UnregisterHotKey(this.Handle, 0); // Unregister hotkey with id 0 before closing the form. You might want to call this more than once with different id values if you are planning to register more than one hotkey.
}
private void button1_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
This is the solution that I've found to listen Key Pressure.
I took the code from "Dylan's Web" and I changed something:
https://www.dylansweb.com/2014/10/low-level-global-keyboard-hook-sink-in-c-net/
This is the class with a couple of lines more:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Input;
namespace DesktopWPFAppLowLevelKeyboardHook
{
public class LowLevelKeyboardListener
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x101;
private const int WM_SYSKEYDOWN = 0x104;
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<KeyPressedArgs> OnKeyPressed;
private LowLevelKeyboardProc _proc;
private IntPtr _hookID = IntPtr.Zero;
public LowLevelKeyboardListener()
{
_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);
int status = 1;
if (OnKeyPressed != null) { OnKeyPressed(this, new KeyPressedArgs(KeyInterop.KeyFromVirtualKey(vkCode), status)); }
}
else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
int status = 0;
if (OnKeyPressed != null) { OnKeyPressed(this, new KeyPressedArgs(KeyInterop.KeyFromVirtualKey(vkCode), status)); }
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
public class KeyPressedArgs : EventArgs
{
public Key KeyPressed { get; private set; }
public int KeyStatus { get; private set; }
public KeyPressedArgs(Key key, int status)
{
KeyPressed = key;
KeyStatus = status;
}
}
}
This is the Form:
using DesktopWPFAppLowLevelKeyboardHook;
using System;
using System.Windows.Forms;
namespace AppLowLevelKeyboardHook
{
public partial class Form1 : Form
{
private LowLevelKeyboardListener _listener;
public int ctrlStatus;
public int cStatus;
public Form1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, EventArgs e)
{
_listener = new LowLevelKeyboardListener();
_listener.OnKeyPressed += _listener_OnKeyPressed;
_listener.HookKeyboard();
}
private void _listener_OnKeyPressed(object sender, KeyPressedArgs e)
{
this.textBox_DisplayKeyboardInput.Text += e.KeyPressed.ToString() + e.KeyStatus.ToString();
if (e.KeyStatus == 1 && (e.KeyPressed.ToString() == "LeftCtrl" || e.KeyStatus.ToString() == "RightCtrl"))
{
if (ctrlStatus == 0)
{
ctrlStatus = ctrlStatus + 1;
timer1.Start();
}
}
else if (e.KeyStatus == 1 && e.KeyPressed.ToString() == "C")
{
if (ctrlStatus == 1 && (cStatus == 0 || cStatus == 2))
{
cStatus = cStatus + 1;
}
}
//no need to wait that CTRL will be released
//else if (e.KeyStatus == 0 && (e.KeyPressed.ToString() == "LeftCtrl" || e.KeyStatus.ToString() == "RightCtrl"))
//{
// if (ctrlStatus == 1)
// {
// ctrlStatus = ctrlStatus + 1;
// }
//}
else if (e.KeyStatus == 0 && e.KeyPressed.ToString() == "C")
{
if (ctrlStatus == 1)
{
if (cStatus == 1 || cStatus == 3)
{
cStatus = cStatus + 1;
}
}
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if ((ctrlStatus == 1) && cStatus == 3 || cStatus == 4) //no need to wait that CTRL will be released (ctrlStatus == 2)
{
//do something
label1.Text = "";
label1.Refresh();
label1.Text = Clipboard.GetText(TextDataFormat.UnicodeText);
//\do something
}
ctrlStatus = 0;
cStatus = 0;
timer1.Stop();
}
private void Window_Closing(object sender, FormClosingEventArgs e)
{
_listener.UnHookKeyboard();
}
}
}
PS: originally it was WPF app but mine it's a WFA and KeyInterop is not included.
So I simply added a reference assembly to WindowsBase.dll
Yesterday I'm back on this question and finally, I did what I was searching to do but changing the strategy because with the upper code I didn't really found a solution for my target. I'm going to report why I think it can be useful to someone else and maybe someone has a different solution.
So, on the upper code, I tried to intercept CTRL+C+C and than get what use copy on the Windows Clipboard. But here it's the problem, that code registers the shortkey, so any other software can't continue to use CTRL+C. I tried to unregister the shortkey after to have cached that CTRL+C has been pressed but I've found only problems. Anyway, I don't like "to stole" a shortkey to another app, I prefer to "listen" key while they are pressed.
So here it follows the code with some comments (I'll paste the solution that I've found "listening keys pressed" adding a new answer).
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
namespace GlobalHotkeyExampleForm
{
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
}
public int shortkey = 0;
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
enum KeyModifier
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
WinKey = 8
}
public Form1()
{
InitializeComponent();
int id = 0; // The id of the hotkey.
RegisterHotKey(this.Handle, id, (int)KeyModifier.Control, Keys.C.GetHashCode()); // Register CTRL + C as global hotkey.
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0312)
{
/* Note that the three lines below are not needed if you only want to register one hotkey.
* The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF); // The key of the hotkey that was pressed.
KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF); // The modifier of the hotkey that was pressed.
int id = m.WParam.ToInt32(); // The id of the hotkey that was pressed.
if (modifier == KeyModifier.Control && key == Keys.C)
{
shortkey = shortkey + 1;
label1.Text = (shortkey.ToString());
//if I enebale this code it works partially. The UnregisterHotKey and RegisterHotKey works perfectly and alone also SendKeys works prefectly, but together it doesn't:
//it doesn't recognize that CTRL is still pressed and I don't want to do CTRL C + CTRL C and I don't like "to stole" the shortkey from other apps!;
//I tried to use SendKeys to resend CTRL C or onlt CTRL but it doens't work and it creates only Register problems
//UnregisterHotKey(this.Handle, 0); //unregister the hotkey catching
//SendKeys.Send("^c"); //send key to active application (it doesn't matter if this is the application)
////register the hotkey catching as C only
//RegisterHotKey(this.Handle, id, (int)KeyModifier.Control, Keys.C.GetHashCode());
//SendKeys.Send("^c");
//SendKeys.Send("^");
timer1.Start();
}
//MessageBox.Show("Hotkey has been pressed!");
// do something
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
UnregisterHotKey(this.Handle, 0); // Unregister hotkey with id 0 before closing the form. You might want to call this more than once with different id values if you are planning to register more than one hotkey.
}
private void button1_Click(object sender, EventArgs e)
{
this.Close();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (shortkey == 2)
{
string returnMyText = Clipboard.GetText(TextDataFormat.Text);
}
shortkey = 0;
timer1.Stop();
}
}
}
Related
Im looking for a way that so Users that use my Program can change the Hotkeys theirself so its not bound to D,F12,K,F,A and so on.. i kinda want that ppl can change it via a Textbox or maybe via a Settings File. Been Stuck on it for couple Weeks now and i just cant find a way to make it happen, its my first Program im working on.
public void gHook_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.D:
// stuff
Salvagebtn.PerformClick();
break;
case Keys.F12:
// stuff
pausebtn.PerformClick();
break;
case Keys.K:
//stuff
Geardropbtn.PerformClick();
break;
case Keys.F:
//stuff
Gamblebtn.PerformClick();
break;
case Keys.A:
//stuff
LeftClickSpambtn.PerformClick();
break;
case Keys.H:
// stuff
openGRbtn.PerformClick();
break;
case Keys.B:
//stuff
gemupbtn.PerformClick();
break;
}
}
I had a few minutes, during commercials. So, I wrote the following:
EDIT : Code wrapped with a global keyboard handler. You will also need to add a reference to PresentationCore and WindowsBase ...
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Input;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
[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);
[DllImport("user32.dll")]
static extern int MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
S_WM_KEYDOWN = 0x0104,
S_WM_KEYUP = 0x0105;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
private static Form1 hookForm;
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);
static bool bShortcutPressed;
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (hookForm.Handle != GetForegroundWindow())
{
if (nCode >= 0 && ((wParam == (IntPtr)WM_KEYDOWN) || (wParam == (IntPtr)S_WM_KEYDOWN)))
{
int vkCode = Marshal.ReadInt32(lParam);
bool bCtrl = (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl));
bool bAlt = (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt));
bool bShift = (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
Keys hookKey = (Keys)vkCode;
hookKey = (bCtrl) ? ((Keys.Control | hookKey)) : hookKey;
hookKey = (bAlt) ? ((Keys.Alt | hookKey)) : hookKey;
hookKey = (bShift) ? ((Keys.Shift | hookKey)) : hookKey;
Debug.Print($"hookKey {hookKey} {bCtrl} {bAlt} {bShift}");
if (!bShortcutPressed && dicTest.ContainsValue(hookKey))
{
hookForm.OnKeyDown(new System.Windows.Forms.KeyEventArgs(hookKey));
bShortcutPressed = true; Debug.Print($"{bShortcutPressed}");
}
}
if (nCode >= 0 && (((wParam == (IntPtr)WM_KEYUP) || (wParam == (IntPtr)S_WM_KEYUP)))) { bShortcutPressed = false; Debug.Print($"{bShortcutPressed}"); }
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
static Dictionary<string, Keys> dicTest = new Dictionary<string, Keys>();
public void AddHotKey(Action function, Keys key, bool ctrl = false, bool shift = false, bool alt = false)
{
KeyDown += delegate (object sender, System.Windows.Forms.KeyEventArgs e) { if (IsHotkey(e, key, ctrl, shift, alt)) { function(); } };
}
public bool IsHotkey(System.Windows.Forms.KeyEventArgs eventData, Keys key, bool ctrl = false, bool shift = false, bool alt = false) =>
eventData.KeyCode == key && eventData.Control == ctrl && eventData.Shift == shift && eventData.Alt == alt;
public Form1() => InitializeComponent();
private void Form1_Load(object sender, EventArgs e)
{
_hookID = SetHook(_proc);
hookForm = this;
KeyPreview = true;
String[] names = Enum.GetNames(typeof(Keys));
foreach (string key in names) { comboBox1.Items.Add(key); }
comboBox1.SelectedItem = comboBox1.Items[0];
KeyDown += Form1_KeyDown;
}
private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
Debug.Print($"Form1_KeyDown : {e.KeyData}");
}
protected override void OnFormClosing(FormClosingEventArgs e) => UnhookWindowsHookEx(_hookID);
private void button1_Click(object sender, EventArgs e)
{
Keys hookKey = (Keys)Enum.Parse(typeof(Keys), comboBox1.Text);
hookKey = (checkBox1.Checked) ? ((Keys.Control | hookKey)) : hookKey;
hookKey = (checkBox2.Checked) ? ((Keys.Alt | hookKey)) : hookKey;
hookKey = (checkBox3.Checked) ? ((Keys.Shift | hookKey)) : hookKey;
if (!dicTest.ContainsValue(hookKey))
{
Debug.Print($"Going to add : {hookKey} : to our Shortcut Dictionary<>");
dicTest.Add("test", hookKey);
AddHotKey(() => { button2.PerformClick(); }, (Keys)new KeysConverter().ConvertFrom(comboBox1.Text), checkBox1.Checked, checkBox3.Checked, checkBox2.Checked);
//checkBox1.Enabled = checkBox2.Enabled = checkBox3.Enabled = comboBox1.Enabled = button1.Enabled = false;
label2.Text = "Go ahead and tryout your Shortcut now ...";
}
else { label2.Text = "Shortcut key is already stored in our Dictionary<> ..."; }
}
private void button2_Click(object sender, EventArgs e) { Debug.Print($"Button2 was clicked"); }
}
}
You're presented with:
Trying the desired shortcut out:
Just an example, to show there are many ways to do things.
Easy to write them out to a file or something. If you were going to ... I'd change the AddHotKey Action param to a string which contains a function name and then using different methods you can have it invoke that function.
Really though, I'd incorporate a Dictionary as the one talked about below.
There's a myriad of different ways you could handle this. Personally, I'd opt for using a Dictionary with the Keycode as the Key, and a delegate/action as the value. Then on the keypress, you just call the function in the dictionary that's associated with that keycode. Something along the lines of:
Dictionary<KeyCode, System.Action> keyDict = new Dictionary<KeyCode, System.Action>();
// populate it however you wish. Whether it's user entry or from a file or something.
keyDict.Add(KeyCode.D, SomeFunction);
If you want to change the delegate in the dictionary:
keyDict[KeyCode.D] = SomeOtherFunction;
Then later in the event handler:
public void gHook_KeyDown(object sender, KeyEventArgs e)
{
keyDict[e.KeyCode].Invoke();
}
public void SomeFunction()
{
Console.WriteLine("called some function");
}
(This question might have a bit more detail if needed: Create dictionary with dynamic keys in C#)
first of all sorry if my english is not that good, this is my first post here, i am developing a C# windows form application, and i want to detect a click from outside the form ( on desktop or somethign else ). I am trying to use MouseKeyHook, because i think it is the solution for my problem, but i can't make it work.. here is my code ( i took it from https://github.com/gmamaladze/globalmousekeyhook, i also tried to replace globalevents to Hook.AppEvents. Anyone can help me to get this working?:
private IKeyboardMouseEvents m_GlobalHook;
public void Subscribe()
{
// Note: for the application hook, use the Hook.AppEvents() instead
m_GlobalHook = Hook.GlobalEvents();
m_GlobalHook.MouseDownExt += GlobalHookMouseDownExt;
m_GlobalHook.KeyPress += GlobalHookKeyPress;
}
private void GlobalHookKeyPress(object sender, KeyPressEventArgs e)
{
Console.WriteLine("KeyPress: \t{0}", e.KeyChar);
}
private void GlobalHookMouseDownExt(object sender, MouseEventExtArgs e)
{
Console.WriteLine("MouseDown: \t{0}; \t System Timestamp: \t{1}", e.Button, e.Timestamp);
// uncommenting the following line will suppress the middle mouse button click
// if (e.Buttons == MouseButtons.Middle) { e.Handled = true; }
}
public void Unsubscribe()
{
m_GlobalHook.MouseDownExt -= GlobalHookMouseDownExt;
m_GlobalHook.KeyPress -= GlobalHookKeyPress;
//It is recommened to dispose it
m_GlobalHook.Dispose();
}
I am using a class independant of type of program (winform, WPF and so on)
its an example:
using System;
using System.Runtime.InteropServices;
using HookInput.API;
namespace HookInput.Mouse
{
public class MouseInput
{
private const Int32 WM_MOUSEMOVE = 0x0200;
private const Int32 WM_LBUTTONDOWN = 0x0201;
private const Int32 WM_LBUTTONUP = 0x0202;
private const Int32 WM_LBUTTONDBLCLK = 0x0203;
private const Int32 WM_RBUTTONDOWN = 0x0204;
private const Int32 WM_RBUTTONUP = 0x0205;
private const Int32 WM_RBUTTONDBLCLK = 0x0206;
private const Int32 WM_MBUTTONDOWN = 0x0207;
private const Int32 WM_MBUTTONUP = 0x0208;
private const Int32 WM_MBUTTONDBLCLK = 0x0209;
private const Int32 WM_MOUSEWHEEL = 0x020A;
private const Int32 WM_XBUTTONDOWN = 0x020B;
private const Int32 WM_XBUTTONUP = 0x020C;
private const Int32 WM_XBUTTONDBLCLK = 0x020D;
private bool hooked = false;
private WindowsHookAPI.HookDelegate mouseDelegate;
private IntPtr mouseHandle;
private const Int32 WH_MOUSE_LL = 14;
public MouseInput()
{
mouseDelegate = MouseHookDelegate;
}
public void setHook(bool on)
{
if (hooked == on) return;
if (on)
{
mouseHandle = WindowsHookAPI.SetWindowsHookEx(WH_MOUSE_LL, mouseDelegate, IntPtr.Zero, 0);
if (mouseHandle != IntPtr.Zero) hooked = true;
}
else
{
WindowsHookAPI.UnhookWindowsHookEx(mouseHandle);
hooked = false;
}
}
private IntPtr MouseHookDelegate(Int32 Code, IntPtr wParam, IntPtr lParam)
{
//VK_LBUTTON 0x01 Left mouse button
//VK_RBUTTON 0x02 Right mouse button
//VK_MBUTTON 0x04 Middle mouse button
//VK_XBUTTON1 0x05 X1 mouse button
//VK_XBUTTON2 0x06 X2 mouse button
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms644970(v=vs.85).aspx
//mouseData:
//If the message is WM_MOUSEWHEEL, the high-order word of this member is the wheel delta.The low-order word is reserved.
// A positive value indicates that the wheel was rotated forward, away from the user;
// a negative value indicates that the wheel was rotated backward, toward the user.
// One wheel click is defined as WHEEL_DELTA, which is 120.(0x78 or 0xFF88)
//If the message is WM_XBUTTONDOWN, WM_XBUTTONUP, WM_XBUTTONDBLCLK, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP, or WM_NCXBUTTONDBLCLK,
// the high - order word specifies which X button was pressed or released,
// and the low - order word is reserved.This value can be one or more of the following values.Otherwise, mouseData is not used.
//XBUTTON1 = 0x0001 The first X button was pressed or released.
//XBUTTON2 = 0x0002 The second X button was pressed or released.
MSLLHOOKSTRUCT lparam = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
int command = (int)wParam;
if (Code < 0 || command == WM_LBUTTONDBLCLK || command == WM_RBUTTONDBLCLK)
return WindowsHookAPI.CallNextHookEx(mouseHandle, Code, wParam, lParam);
if (command == WM_MOUSEMOVE)
{
Console.WriteLine($"x:{lparam.pt.x} y:{lparam.pt.y}");
}
else if (command == WM_LBUTTONDOWN || command == WM_LBUTTONUP)
{
if (command == WM_LBUTTONDOWN)
{
//do something
}
else
{
//do something
}
}
else if (command == WM_RBUTTONDOWN || command == WM_RBUTTONUP)
{
if (command == WM_RBUTTONDOWN)
{
//do something
}
else
{
//do something
}
}
else if (command == WM_MBUTTONDOWN || command == WM_MBUTTONUP)
{
if (command == WM_MBUTTONDOWN)
{
//do something
}
else
{
//do something
}
}
else if (command == WM_MOUSEWHEEL)
{
int wheelvalue = (Int16)(lparam.mouseData >> 16) < 0 ? -120 : 120; // Forward = 120, Backward = -120
}
return WindowsHookAPI.CallNextHookEx(mouseHandle, Code, wParam, lParam);
}
~MouseInput()
{
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
}
}
the file API:
using System;
using System.Runtime.InteropServices;
namespace HookInput.API
{
public class WindowsHookAPI
{
//public delegate IntPtr HookDelegate(
// Int32 Code, IntPtr wParam, IntPtr lParam);
public delegate IntPtr HookDelegate(Int32 Code, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hHook, Int32 nCode, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
public static extern IntPtr UnhookWindowsHookEx(IntPtr hHook);
[DllImport("User32.dll")]
public static extern IntPtr SetWindowsHookEx(Int32 idHook, HookDelegate lpfn, IntPtr hmod, Int32 dwThreadId);
}
}
you can instance the class
private MouseInput mouseInputHook = null;
mouseInputHook = new MouseInput();
mouseInputHook.setHook(true); // to activate the mousehook
:
:
mouseInputHook.setHook(false); // to desactivate the mousehook
I encounter a problem to make my Office Addin works with my global keyboard on Powerpoint 2013 but not on the previous versions (2007 and 2010).
I do not get any exception but it seems that the OnKeyDown event is never triggered on Powerpoint 2013, and I don't know why.
I get the same problems on all versions of Windows (XP, 7, 8 & 8.1), on 32 & 64 bits environments. The version of Microsoft Office is 32 bits.
Here is a code sample :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Office = Microsoft.Office.Core;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace testHook
{
public partial class ThisAddIn
{
Hook hook;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
hook = new Hook(Hook.HookType.KeyBoard, Hook.HookVisibility.Global);
hook.OnKeyDown += new KeyEventHandler(hook_OnKeyDown);
hook.Start();
}
void hook_OnKeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show(e.KeyCode.ToString());
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
hook.Stop();
hook = null;
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
class Hook
{
private IntPtr m_Hook = (IntPtr)0;
private HookVisibility m_Visibility;
private HookType m_HookType;
private HookProc m_Proc;
public enum HookType { KeyBoard };
public enum KeyBoardEventType { KeyDown, KeyUp, SysKeyDown, SysKeyUp, KeyShift, KeyCapital, NumLock };
public enum HookVisibility { Global, Local };
private delegate IntPtr HookProc(int nCode, int wParam, IntPtr lParam);
private KeyPressEventHandler m_onKeyPress;
private KeyEventHandler m_onKeyUp;
private KeyEventHandler m_onKeyDown;
public event KeyPressEventHandler OnKeyPress
{
add
{
m_onKeyPress += value;
}
remove
{
m_onKeyPress -= value;
}
}
public event KeyEventHandler OnKeyUp
{
add
{
m_onKeyUp += value;
}
remove
{
m_onKeyUp -= value;
}
}
public event KeyEventHandler OnKeyDown
{
add
{
m_onKeyDown += value;
}
remove
{
m_onKeyDown -= value;
}
}
#region DLLImport
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hmod, int dwThreadId);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hHook);
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(IntPtr lpModuleName);
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(String lpModuleName);
[DllImport("Kernel32.dll")]
private static extern IntPtr GetCurrentThreadId();
[DllImport("user32")]
private static extern int ToAscii(int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern short GetKeyState(int vKey);
[DllImport("user32")]
private static extern int GetKeyboardState(byte[] pbKeyState);
#endregion
[StructLayout(LayoutKind.Sequential)]
private class KeyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
public Hook(HookType H, HookVisibility H2)
{
m_HookType = H;
m_Visibility = H2;
}
public bool Start()
{
if (m_HookType == HookType.KeyBoard)
m_Proc = new HookProc(KeyProc);
if (m_Visibility == HookVisibility.Global)
m_Hook = SetWindowsHookEx(getHookType(m_HookType, m_Visibility), m_Proc, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);
else if (m_Visibility == HookVisibility.Local)
m_Hook = SetWindowsHookEx(getHookType(m_HookType, m_Visibility), m_Proc, GetModuleHandle((IntPtr)0), (int)GetCurrentThreadId());
if (m_Hook == (IntPtr)0)
return false;
return true;
}
public bool Stop()
{
return UnhookWindowsHookEx(m_Hook);
}
private int getHookType(HookType H, HookVisibility V)
{
if (H == HookType.KeyBoard && V == HookVisibility.Local)
return 2;
if (H == HookType.KeyBoard && V == HookVisibility.Global)
return 13;
else return -1;
}
private int getKeyBoardEventType(KeyBoardEventType K)
{
if (K == KeyBoardEventType.KeyDown)
return 0x100;
if (K == KeyBoardEventType.KeyUp)
return 0x101;
if (K == KeyBoardEventType.SysKeyDown)
return 0x104;
if (K == KeyBoardEventType.SysKeyUp)
return 0x105;
if (K == KeyBoardEventType.KeyShift)
return 0x10;
if (K == KeyBoardEventType.KeyCapital)
return 0x14;
if (K == KeyBoardEventType.NumLock)
return 0x90;
else return -1;
}
private IntPtr KeyProc(int nCode, int wParam, IntPtr lParam)
{
bool handled = false;
if ((nCode >= 0) && (m_onKeyDown != null || m_onKeyUp != null || m_onKeyPress != null))
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
if (m_onKeyDown != null && (wParam == 0x100 || wParam == 0x104))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
m_onKeyDown(this, e);
handled = handled || e.Handled;
}
if (m_onKeyPress != null && wParam == 0x100)
{
bool isShift = ((GetKeyState(0x10) & 0x80) == 0x80 ? true : false);
bool isCapslock = (GetKeyState(0x14) != 0 ? true : false);
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
{
char key = (char)inBuffer[0];
if ((isCapslock ^ isShift) && Char.IsLetter(key))
key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
m_onKeyPress(this, e);
handled = handled || e.Handled;
}
}
if (m_onKeyUp != null && (wParam == 0x101 || wParam == 0x105))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
m_onKeyUp(this, e);
handled = handled || e.Handled;
}
}
if (handled)
return (IntPtr)1;
else
return CallNextHookEx(m_Hook, nCode, (IntPtr)wParam, (IntPtr)lParam);
}
}
}
My application need to fire events during the slideshow because I have some others windows which are displayed during the presentation, and I have to update them according to the keys that the user presses. I have tried a lot of solutions but the hook is the only one that can do perfectly the job.
I tried too a local keyboard hook instead of a global one. I actually think that it is the only way to make it works because it is a bug from Microsoft, and not from the code. However, I can't make the local one works properly on any version of Powerpoint.
The issue is not Powerpoint specific, it occurs with any Office product.
I'm working on a Outlook addin and fancing this problem.
It has been reported (without any answer unfortunely) to Microsoft:
https://social.msdn.microsoft.com/Forums/office/en-US/93d08ccc-9e77-4f72-9c51-477468d89681/keyboardhook-will-not-work-in-word-2013?forum=worddev
I have been able to make a "workaround": i have register a Global Hook AND a Local one ("Thread"). It works, but the keyProc is called "weirdly" when it's local, not respecting documented parameters:
- wParam is not one of WM_*, but contains directly the vkCode
- lParam cannot be accessed (AccessViolation)
As I understood, it's because some "TranslateMessage" processing.
I have also adapted your code to be able to catch ALT combination.
private bool wParamAlt;
private IntPtr KeyProc(int nCode, int wParam, IntPtr lParam)
{
bool handled = false;
if ((nCode == 0) && (m_onKeyDown != null || m_onKeyUp != null || m_onKeyPress != null))
{
KeyboardHookStruct MyKeyboardHookStruct;
if (wParam >= 0x100)
{
MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
wParamAlt = false;
}
else
{
MyKeyboardHookStruct = new KeyboardHookStruct();
MyKeyboardHookStruct.vkCode = wParam;
if (wParamAlt)
{
wParamAlt = (wParam == 18);
wParam = 0x104;
}
else
{
wParamAlt = (wParam == 18);
wParam = 0x100;
}
}
if (m_onKeyDown != null && (wParam == 0x100 || wParam == 0x104))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
if (wParam == 0x104)
keyData |= Keys.Alt;
KeyEventArgs e = new KeyEventArgs(keyData);
m_onKeyDown(this, e);
handled = handled || e.Handled;
}
if (m_onKeyPress != null && wParam == 0x100)
{
bool isShift = ((GetKeyState(0x10) & 0x80) == 0x80 ? true : false);
bool isCapslock = (GetKeyState(0x14) != 0 ? true : false);
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
{
char key = (char)inBuffer[0];
if ((isCapslock ^ isShift) && Char.IsLetter(key))
key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
m_onKeyPress(this, e);
handled = handled || e.Handled;
}
}
if (m_onKeyUp != null && (wParam == 0x101 || wParam == 0x105))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
if (wParam == 0x105)
keyData |= Keys.Alt;
KeyEventArgs e = new KeyEventArgs(keyData);
m_onKeyUp(this, e);
handled = handled || e.Handled;
}
}
if (handled)
return (IntPtr)1;
else
return CallNextHookEx(m_Hook, nCode, (IntPtr)wParam, (IntPtr)lParam);
}
I try to configure some global Hotkeys in a C# WinForm Application that should work with and without focus. For this I played a little bit with some Hooking Librarys like: Link
But these Libraries are not working really reliable so that I decided to look for a different way. And I found one but there is one problem: With the new method posted below I can catch a keypress, but I like to catch also the keydown (WM_KEYDOWN/0x100) and keyup event... But this is not working. Any ideas?
Code:
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
const int MOD_CONTROL = 0x0002;
const int MOD_SHIFT = 0x0004;
const int WM_HOTKEY = 0x0312;
const int WM_KEYDOWN = 0x0100;
private void Form1_Load(object sender, EventArgs e)
{
// Hook Keys
RegisterHotKey(this.Handle, 1, MOD_CONTROL, (int)0);
RegisterHotKey(this.Handle, 2, MOD_CONTROL, (int)Keys.X);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
UnregisterHotKey(this.Handle, 1);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY && (int)m.WParam == 1)
{
MessageBox.Show("here");
}
if (m.Msg == WM_KEYDOWN && (int)m.WParam == 2)
this.Opacity = 100;
base.WndProc(ref m);
}
Does anyone know how to use the RegisterHotKey/UnregisterHotKey API calls in a console application? I assume that setting up/removing the hotkey is the same, but how do I get the call back when the key was pressed?
Every example I see is for Winforms, and uses protected override void WndProc(ref Message m){...}, which isn't available to me.
update: what I have is below, but the event is never hit. I thought it could be because when you load ConsoleShell it does block further execution, but even if I put SetupHotkey into a different thread nothing happens. Any thoughts?
class Program
{
static void Main(string[] args)
{
new Hud().Init(args);
}
}
class Hud
{
int keyHookId;
public void Init(string[] args)
{
SetupHotkey();
InitPowershell(args);
Cleanup();
}
private void Cleanup()
{
HotKeyManager.UnregisterHotKey(keyHookId);
}
private void SetupHotkey()
{
keyHookId = HotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Control);
HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
}
void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
{
//never executed
System.IO.File.WriteAllText("c:\\keyPressed.txt", "Hotkey pressed");
}
private static void InitPowershell(string[] args)
{
var config = RunspaceConfiguration.Create();
ConsoleShell.Start(config, "", "", args);
}
}
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!");
}
}
}
I just wanted to offer an alternative solution.
I was answering a question for someone who was using this script and I figured this might help someone else who has trouble setting up a global key hook.
Edit: Don't forget to add a reference to System.Windows.Forms
You can do this by selecting Projectš¢Add Reference and checking System.Windows.Forms
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ConsoleKeyhook
{
class Hooky
{
///////////////////////////////////////////////////////////
//A bunch of DLL Imports to set a low level keyboard hook
///////////////////////////////////////////////////////////
[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);
////////////////////////////////////////////////////////////////
//Some constants to make handling our hook code easier to read
////////////////////////////////////////////////////////////////
private const int WH_KEYBOARD_LL = 13; //Type of Hook - Low Level Keyboard
private const int WM_KEYDOWN = 0x0100; //Value passed on KeyDown
private const int WM_KEYUP = 0x0101; //Value passed on KeyUp
private static LowLevelKeyboardProc _proc = HookCallback; //The function called when a key is pressed
private static IntPtr _hookID = IntPtr.Zero;
private static bool CONTROL_DOWN = false; //Bool to use as a flag for control key
public static void Main()
{
_hookID = SetHook(_proc); //Set our hook
Application.Run(); //Start a standard application method loop
}
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) //A Key was pressed down
{
int vkCode = Marshal.ReadInt32(lParam); //Get the keycode
string theKey = ((Keys)vkCode).ToString(); //Name of the key
Console.Write(theKey); //Display the name of the key
if (theKey.Contains("ControlKey")) //If they pressed control
{
CONTROL_DOWN = true; //Flag control as down
}
else if (CONTROL_DOWN && theKey == "B") //If they held CTRL and pressed B
{
Console.WriteLine("\n***HOTKEY PRESSED***"); //Our hotkey was pressed
}
else if (theKey == "Escape") //If they press escape
{
UnhookWindowsHookEx(_hookID); //Release our hook
Environment.Exit(0); //Exit our program
}
}
else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP) //KeyUP
{
int vkCode = Marshal.ReadInt32(lParam); //Get Keycode
string theKey = ((Keys)vkCode).ToString(); //Get Key name
if (theKey.Contains("ControlKey")) //If they let go of control
{
CONTROL_DOWN = false; //Unflag control
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam); //Call the next hook
}
}
}
I came up with a solution based on Chris' answer that uses WPF instead of WinForms:
public sealed class GlobalHotkeyRegister : IGlobalHotkeyRegister, IDisposable
{
private const int WmHotkey = 0x0312;
private Application _app;
private readonly Dictionary<Hotkey, Action> _hotkeyActions;
public GlobalHotkeyRegister()
{
_hotkeyActions = new Dictionary<Hotkey, Action>();
var startupTcs = new TaskCompletionSource<object>();
Task.Run(() =>
{
ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage;
_app = new Application();
_app.Startup += (s, e) => startupTcs.SetResult(null);
_app.Run();
});
startupTcs.Task.Wait();
}
public void Add(Hotkey hotkey, Action action)
{
_hotkeyActions.Add(hotkey, action);
var keyModifier = (int) hotkey.KeyModifier;
var key = KeyInterop.VirtualKeyFromKey(hotkey.Key);
_app.Dispatcher.Invoke(() =>
{
if (!RegisterHotKey(IntPtr.Zero, hotkey.GetHashCode(), keyModifier, key))
throw new Win32Exception(Marshal.GetLastWin32Error());
});
}
public void Remove(Hotkey hotkey)
{
_hotkeyActions.Remove(hotkey);
_app.Dispatcher.Invoke(() =>
{
if (!UnregisterHotKey(IntPtr.Zero, hotkey.GetHashCode()))
throw new Win32Exception(Marshal.GetLastWin32Error());
});
}
private void OnThreadPreProcessMessage(ref MSG msg, ref bool handled)
{
if (msg.message != WmHotkey)
return;
var key = KeyInterop.KeyFromVirtualKey(((int) msg.lParam >> 16) & 0xFFFF);
var keyModifier = (KeyModifier) ((int) msg.lParam & 0xFFFF);
var hotKey = new Hotkey(keyModifier, key);
_hotkeyActions[hotKey]();
}
public void Dispose()
{
_app.Dispatcher.InvokeShutdown();
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
public class Hotkey
{
public Hotkey(KeyModifier keyModifier, Key key)
{
KeyModifier = keyModifier;
Key = key;
}
public KeyModifier KeyModifier { get; }
public Key Key { get; }
#region ToString(), Equals() and GetHashcode() overrides
}
[Flags]
public enum KeyModifier
{
None = 0x0000,
Alt = 0x0001,
Ctrl = 0x0002,
Shift = 0x0004,
Win = 0x0008,
NoRepeat = 0x4000
}
To use this, you need to add references to PresentationFramework.dll and WindowsBase.dll.
public static void Main()
{
using (var hotkeyManager = new GlobalHotkeyManager())
{
var hotkey = new Hotkey(KeyModifier.Ctrl | KeyModifier.Alt, Key.S);
hotkeyManager.Add(hotkey, () => System.Console.WriteLine(hotkey));
System.Console.ReadKey();
}
}
Changed the HotKeyManager class
public static class HotKeyManager
{
public static event EventHandler<HotKeyEventArgs> HotKeyPressed;
public static int RegisterHotKey(Keys key, HotKeyEventArgs.KeyModifiers modifiers)
{
_windowReadyEvent.WaitOne();
_wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, Interlocked.Increment(ref _id), (uint)modifiers, (uint)key);
return Interlocked.Increment(ref _id);
}
public static void UnregisterHotKey(int id)
{
_wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
}
private delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
private delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);
private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
{
RegisterHotKey(hWnd: hwnd, id: id, fsModifiers: modifiers, vk: key);
}
private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
{
UnregisterHotKey(_hwnd, id);
}
private static void OnHotKeyPressed(HotKeyEventArgs e)
{
HotKeyPressed?.Invoke(null, e);
}
private static volatile MessageWindow _wnd;
private static volatile IntPtr _hwnd;
private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
static HotKeyManager()
{
new Thread(delegate ()
{
Application.Run(new MessageWindow());
})
{
Name = "MessageLoopThread",
IsBackground = true
}.Start();
}
private class MessageWindow : Form
{
public MessageWindow()
{
_wnd = this;
_hwnd = Handle;
_windowReadyEvent.Set();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY)
{
var e = new HotKeyEventArgs(hotKeyParam: m.LParam);
OnHotKeyPressed(e);
}
base.WndProc(m: 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;
}
Class HotKeyEventArgs:
public partial class HotKeyEventArgs : EventArgs
{
public readonly Keys Key;
public readonly KeyModifiers Modifiers;
public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
{
Key = key;
Modifiers = modifiers;
}
public HotKeyEventArgs(IntPtr hotKeyParam)
{
Key = (Keys)(((uint)hotKeyParam.ToInt64() & 0xffff0000) >> 16);
Modifiers = (KeyModifiers)((uint)hotKeyParam.ToInt64() & 0x0000ffff);
}
}
And class: HotKeyEventArgs
public partial class HotKeyEventArgs
{
[Flags]
public enum KeyModifiers
{
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
NoRepeat = 0x4000
}
}