Currently, I can open & close the windows touch keyboard,
I don't know how to detect the touch keyboard is popped up or not,
so I try to get windows style value,
when I click the button at the top-right corner, to hiding the touch keyboard, please reference this image
the windows style value is the same as I did not hide the touch keyboard,
Is there any way to detect this situation?
---Test code as below -------------------------------------------------------
namespace WindowsFormsApp7
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CloseWindowsTouchKeyboard();
}
private void btnOpen_Click(object sender, EventArgs e)
{
OpenWindowsTouchKeyboard();
}
private void btnClose_Click(object sender, EventArgs e)
{
CloseWindowsTouchKeyboard();
}
private void btnGetWindowLong_Click(object sender, EventArgs e)
{
GetWindowLong();
}
private const string IPTIP_MAIN_WINDOW_CLASS_NAME = "IPTip_Main_Window";
private const int GWL_STYLE = -16;
private const int WM_SYSCOMMAND = 274;
private const uint SC_CLOSE = 61536;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool PostMessage(IntPtr hWnd, int Msg, uint wParam, uint lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
public static void OpenWindowsTouchKeyboard()
{
string strFilePath = #"C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe";
if (!File.Exists(strFilePath))
{
return;
}
Process.Start(strFilePath);
}
public static void CloseWindowsTouchKeyboard()
{
IntPtr TouchhWnd;
TouchhWnd = FindWindow(IPTIP_MAIN_WINDOW_CLASS_NAME, null);
if (TouchhWnd == IntPtr.Zero)
{
return;
}
PostMessage(TouchhWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
}
public static void GetWindowLong()
{
IntPtr tKeyboardHandle = FindWindow(IPTIP_MAIN_WINDOW_CLASS_NAME, null);
if (tKeyboardHandle != IntPtr.Zero)
{
uint iStyle = GetWindowLong(tKeyboardHandle, GWL_STYLE);
Console.WriteLine($"iStyle: {iStyle}");
}
}
}
}
You'll need to utilize ComImport to define interfaces manually:
[ComImport, Guid("228826af-02e1-4226-a9e0-99a855e455a6")]
class ImmersiveShellBroker { }
[ComImport, Guid("9767060c-9476-42e2-8f7b-2f10fd13765c")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IImmersiveShellBroker
{
void M();
IInputHostManagerBroker GetInputHostManagerBroker();
}
[ComImport, Guid("2166ee67-71df-4476-8394-0ced2ed05274")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IInputHostManagerBroker { void GetIhmLocation(out Bounds rect, out DisplayMode mode); }
[StructLayout(LayoutKind.Sequential)]
struct Bounds { public int Left, Top, Right, Bottom; }
enum DisplayMode { None = 0, Floating = 2, Docked = 3 }
Then you can use them to check the touch keyboard display mode like so,
IImmersiveShellBroker broker = (IImmersiveShellBroker)(new ImmersiveShellBroker());
IInputHostManagerBroker inputHost = broker.GetInputHostManagerBroker();
Marshal.ReleaseComObject(broker);
// if mode != DisplayMode.None then the keyboard is open
DisplayMode mode;
inputHost.GetIhmLocation(out _, out mode);
you can also cache the inputHost somewhere, but it's not necessary.
Related
I have a wpf application which is in maximized state always without showing taskbar.
Here is the code for Hiding and showing taskbar.
[DllImport("user32.dll")]
private static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hwnd, int command);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
static int hwnd = FindWindow("Shell_TrayWnd", "");
public static new void Hide()
{
ShowWindow(hwnd, SW_HIDE);
}
public static new void Show()
{
ShowWindow(hwnd, SW_SHOW);
}
This is working fine on windows 7. But when application runs on Windows 10.. taskbar didnt show up again by calling show().
Here is the part where I am calling show()
#region Show Desktop
private void Desktop_MouseUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left )
{
this.WindowState = System.Windows.WindowState.Minimized;
Shell32.Shell objShel = new Shell32.Shell();
objShel.MinimizeAll();
Show();
}
}
#endregion
This works on the main display and is taken from here and converted to c#.
public static class Taskbar
{
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr FindWindow(
string lpClassName,
string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int x,
int y,
int cx,
int cy,
uint uFlags
);
[Flags]
private enum SetWindowPosFlags : uint
{
HideWindow = 128,
ShowWindow = 64
}
public static void Show()
{
var window = FindWindow("Shell_traywnd", "");
SetWindowPos(window, IntPtr.Zero, 0, 0, 0, 0, (uint) SetWindowPosFlags.ShowWindow);
}
public static void Hide()
{
var window = FindWindow("Shell_traywnd", "");
SetWindowPos(window, IntPtr.Zero, 0, 0, 0, 0, (uint)SetWindowPosFlags.HideWindow);
}
}
I am creating a Screen Recording app using Windows media encoder, i Can record the video and save it to a disk,now i need to draw a filled circle around the mouse click area when ever the mouse click event Fire( to high light the area that mouse press event occur), is there a way to do that? any code sample? thank you!!! sorry for not inserting the code here is my code
public void CaptureMoni()
{
string dateTime = DateTime.Now.ToString("yyyy.MM.dd _ HH.mm");
var dir = #"C:\VIDEOS\" + dateTime;
try
{
System.Drawing.Rectangle _screenRectangle = Screen.PrimaryScreen.Bounds;
_screenCaptureJob = new ScreenCaptureJob();
_screenCaptureJob.CaptureRectangle = _screenRectangle;
_screenCaptureJob.ShowFlashingBoundary = true;
_screenCaptureJob.ScreenCaptureVideoProfile.FrameRate = 10;
_screenCaptureJob.CaptureMouseCursor = true;
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
_screenCaptureJob.OutputScreenCaptureFileName = string.Format(Path.Combine(dir, dateTime + ".wmv"));
}
}
catch (Exception)
{
MessageBox.Show("Screnn Capturing Failed!" );
}
string temPath = (_screenCaptureJob.OutputScreenCaptureFileName.ToString());
// MessageBox.Show(temPath);
}
public ScreenCaptureJob _screenCaptureJob { get; set; }
finally i did what i need Special thanks to PacMani he direct me the right way,
NOTE : this is not a full code which include red circle in the video, this is only for having a red circle and making the transparent form on top of the all the other forms (sorry for the language mistakes)) thank you for the Help # PacMAni
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
WindowState = FormWindowState.Maximized;
this.TopMost = true;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)RMouseListener.WM.WM_NCHITTEST)
m.Result = (IntPtr)RMouseListener.WM.HTTRANSPARENT;
else
base.WndProc(ref m);
}
public void draw_circle()
{
int x = MousePosition.X;
int y = MousePosition.Y;
System.Drawing.SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Salmon);
System.Drawing.Graphics formGraphics = this.CreateGraphics();
formGraphics.FillEllipse(myBrush, new Rectangle(x - 60, y - 60, 60, 60));
myBrush.Dispose();
formGraphics.Dispose();
Thread.Sleep(200);
this.Invalidate();
}
RMouseListener _native;
private void button1_Click(object sender, EventArgs e)
{
//_native = new RMouseListener();
//_native.RButtonClicked += new EventHandler<SysMouseEventInfo>(_native_RButtonClicked);
//_native.LButtonClicked += new EventHandler<SysMouseEventInfo>(_native_LButtonClicked);
}
private void button2_Click(object sender, EventArgs e)
{
_native.Close();
this.Dispose();
}
void _native_RButtonClicked(object sender, SysMouseEventInfo e)
{
// listBox1.Items.Add(e.WindowTitle);
draw_circle();
}
void _native_LButtonClicked(object sender, SysMouseEventInfo e)
{
// listBox2.Items.Add(e.WindowTitle);
draw_circle();
}
// }
public class SysMouseEventInfo : EventArgs
{
public string WindowTitle { get; set; }
}
public class RMouseListener
{
Form1 frm = new Form1();
public RMouseListener()
{
this.CallBack += new HookProc(MouseEvents);
//Module mod = Assembly.GetExecutingAssembly().GetModules()[0];
//IntPtr hMod = Marshal.GetHINSTANCE(mod);
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
{
IntPtr hModule = GetModuleHandle(module.ModuleName);
_hook = SetWindowsHookEx(WH_MOUSE_LL, this.CallBack, hModule, 0);
}
}
int WH_MOUSE_LL = 14;
int HC_ACTION = 0;
HookProc CallBack = null;
IntPtr _hook = IntPtr.Zero;
public event EventHandler<SysMouseEventInfo> RButtonClicked;
public event EventHandler<SysMouseEventInfo> LButtonClicked;
int MouseEvents(int code, IntPtr wParam, IntPtr lParam)
{
//Console.WriteLine("Called");
//MessageBox.Show("Called!");
if (code < 0)
return CallNextHookEx(_hook, code, wParam, lParam);
if (code == this.HC_ACTION)
{
// Left button pressed somewhere
if (wParam.ToInt32() == (uint)WM.WM_RBUTTONDOWN || wParam.ToInt32() == (uint)WM.WM_LBUTTONDOWN)
{
MSLLHOOKSTRUCT ms = new MSLLHOOKSTRUCT();
ms = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
IntPtr win = WindowFromPoint(ms.pt);
string title = GetWindowTextRaw(win);
if (RButtonClicked != null || LButtonClicked != null)
{
RButtonClicked(this, new SysMouseEventInfo { WindowTitle = title });
LButtonClicked(this, new SysMouseEventInfo { WindowTitle = title });
}
}
}
return CallNextHookEx(_hook, code, wParam, lParam);
}
public void Close()
{
if (_hook != IntPtr.Zero)
{
UnhookWindowsHookEx(_hook);
}
}
public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SetWindowsHookEx", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll")]
static extern IntPtr WindowFromPoint(int xPoint, int yPoint);
[DllImport("user32.dll")]
static extern IntPtr WindowFromPoint(POINT Point);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, [Out] StringBuilder lParam);
public static string GetWindowTextRaw(IntPtr hwnd)
{
// Allocate correct string length first
//int length = (int)SendMessage(hwnd, (int)WM.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
StringBuilder sb = new StringBuilder(65535);//THIS COULD BE BAD. Maybe you shoudl get the length
SendMessage(hwnd, (int)WM.WM_GETTEXT, (IntPtr)sb.Capacity, sb);
return sb.ToString();
}
[StructLayout(LayoutKind.Sequential)]
public struct MSLLHOOKSTRUCT
{
public POINT pt;
public int mouseData;
public int flags;
public int time;
public UIntPtr dwExtraInfo;
}
public enum WM : uint
{//all windows messages here
WM_LBUTTONDOWN = 0x0201,
WM_RBUTTONDOWN = 0x0204,
WM_GETTEXT = 0x000D,
WM_GETTEXTLENGTH = 0x000E,
WM_MOUSE = 0x0200,
WM_NCHITTEST = 0x84,
HTTRANSPARENT
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
_native = new RMouseListener();
_native.RButtonClicked += new EventHandler<SysMouseEventInfo>(_native_RButtonClicked);
_native.LButtonClicked += new EventHandler<SysMouseEventInfo>(_native_LButtonClicked);
}
Screen recording applications add this circle in their video file, not on the desktop.
If you still want to create a circle window, you need a form with the two things you have to implement:
Click-through window (otherwise the cursor could not click on things behind it anymore). See here for the solution: Click through transparency for Visual C# Window Forms?
Transparent window (transparency key might be enough here already, if you want alpha-blended transparency, things get more exciting, there are many examples for that on CodeProject, for example http://www.codeproject.com/Articles/20758/Alpha-Blended-Windows-Forms). Without this, you would have a rectangular window and not just a circle.
Also you need to register a global mouse hook to detect global mouse clicks. I think you already did this, but your question does not show what you've done and what not.
If you received a mouse click, create a borderless window around the mouse coordinates and (with the background color being the same as the transparency key to make it invisible where the circle isn't drawn) draw an ellipse on it (hook the Paint-event and use the Graphics objects DrawEllipse method).
Start a timer after which this window disappears again (or the circle would be visible forever). If you want this circle to animate as in other screen recording apps, use the timer to animate the drawing.
I want to show virtual keyboard when i click on textbox. Any ideas how to implement virtual keyboard to my app?
This code does nothing at all.
private void textBox1_GotFocus(object sender, EventArgs e)
{
inputPanel1.Enabled = true;
}
private void textBox1_LostFocus(object sender, EventArgs e)
{
inputPanel1.Enabled = false;
}
LostFocus handler removing helped.
than i had to change position of keyboard. This is the only way to do it cuz of non existing property ://.. Maybe it will be useful for someone.
[DllImport("coredll.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SipGetInfo(
ref SIPINFO sipInfo);
[DllImport("coredll.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SipSetInfo(
ref SIPINFO sipInfo);
[StructLayout(LayoutKind.Sequential)]
public struct SIPINFO
{
public uint cbSize;
public uint fdwFlags;
public RECT rcVisibleDesktop;
public RECT rcSipRect;
public uint dwImDataSize;
public IntPtr pvImData;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
private void ShowInputPanel(Control control)
{
InputPanel.SIPINFO sipInfo;
var x = 100;
var y = control.PointToScreen(new Point(110, 150)).Y; //control.Height
this.inputPanel1.Enabled = true;
sipInfo = new InputPanel.SIPINFO();
sipInfo.cbSize = (uint)Marshal.SizeOf(sipInfo);
if (InputPanel.SipGetInfo(ref sipInfo))
{
sipInfo.rcSipRect.left = x;
sipInfo.rcSipRect.top = y;
InputPanel.SipSetInfo(ref sipInfo);
}
}
private void textBox1_GotFocus(object sender, EventArgs e)
{
this.ShowInputPanel(this.textBox1);
}
is it possible for it to detect and differentiate cut,copy, or paste of files? I only can detect a change in the clipboard to far.
public partial class Form1 : Form
{
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWnd);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
private IntPtr _ClipboardViewerNext;
private const int WM_DRAWCLIPBOARD = 0x0308;
// private const int WM_CUT = 0x0301;
public Form1()
{
InitializeComponent();
}
private void StartClipboardViewer()
{
_ClipboardViewerNext = SetClipboardViewer(this.Handle);
}
private void StopClipboardViewer()
{
ChangeClipboardChain(this.Handle, _ClipboardViewerNext);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_DRAWCLIPBOARD)
{
MessageBox.Show("Copied");
SendMessage(_ClipboardViewerNext, m.Msg, m.WParam, m.LParam);
}
else
{
base.WndProc(ref m);
}
}
private void Form1_Load(object sender, EventArgs e)
{
StartClipboardViewer();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
StopClipboardViewer();
}
}
No, but you could write a wrapper for the Clipboard (as it's a sealed class you can't derive from it) to keep track of the get/set operations.
The clipboard does not differentiate between cut and copy. It's a semantic difference in the way that the source application treats the data (or files).
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
}
}