I'm trying to implement a color picker that takes the color from a pixel everywhere in the screen.
To do that I'm planning to use a global mouse hook to listen to WM_MOUSEMOVE in order to update the color as the mouse is moved around and listen to mouse clicks to confirm (WM_LBUTTONDOWN) or cancel(WM_RBUTTONDOWN) the operation.
I have followed one of the many tutorials around and I came up with this (in a Console Application, just to test out if the process works):
static IntPtr hook;
static bool click;
static NativeMethods.LowLevelHookStruct llhs;
static void Main(string[] args)
{
hook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, MouseHookCallback, (IntPtr)null, 0);
if (hook != IntPtr.Zero)
{
Console.WriteLine("Hook Set");
while (!Console.KeyAvailable) {
Console.WriteLine("{0} {1} {2}", hook, llhs.pt.x, llhs.pt.y);
if(click) Console.WriteLine("click!");
click = false;
System.Threading.Thread.Sleep(250);
}
}
}
and
public static IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
NativeMethods.LowLevelHookStruct hookStruct = (NativeMethods.LowLevelHookStruct)Marshal.PtrToStructure(lParam, typeof(NativeMethods.LowLevelHookStruct));
if (NativeMethods.MouseMessages.WM_MOUSEMOVE == (NativeMethods.MouseMessages)wParam)
{
llhs = hookStruct;
}
if (NativeMethods.MouseMessages.WM_LBUTTONDOWN == (NativeMethods.MouseMessages)wParam)
{
click = true;
}
else if (NativeMethods.MouseMessages.WM_RBUTTONDOWN == (NativeMethods.MouseMessages)wParam)
{
}
}
return NativeMethods.CallNextHookEx(hook, nCode, wParam, lParam);
}
NativeMethods is simply a class where I keep all the DllImport related stuff.
Once I run the console application, the mouse cursor gets stuck for a couple of seconds, and in the console I get this - even while the cursor is stuck
Hook Set
3945554872 0 0
3945554872 0 0
3945554872 0 0
3945554872 0 0
...
Going at it in debug, it seems that my hook is never called, not even once.
Any idea what might be wrong?
Following #Hans Passant comment I moved my test code to a WinForms application, and callbacks started coming.
Then it was just a matter to find out that the callback was being garbage collected, so all I had to do was to change
hook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, MouseHookCallback, (IntPtr)null, 0);
to
private NativeMethods.LowLevelHookProc _hookCallback;
...
_hookCallback = new NativeMethods.LowLevelHookProc(MouseHookCallback);
hook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, _hookCallback, (IntPtr)null, 0);
in order to keep a reference to the callback so that it would not be GCed.
Related
Appreciate guidance and help here please;
I have searched and tried many solutions and it seems non are working with .Net 6 when I tried to center the MessageBox.Show() at the center of the parent Form!
So I have followed this:
Winforms-How can I make MessageBox appear centered on MainForm?
AND
How to Make MessageBoxes Center on their Parent Forms
When I tried to debug the code above and step through, the hook is not being called. Win32 is a wrapper calss of User32.dll
public static DialogResult Show(string msg, string title, MessageBoxButtons btns, MessageBoxIcon icon)
{
// Create a callback delegate
_hookProcDelegate = new Win32.WindowsHookProc(HookCallback);
// Remember the title & message that we'll look for.
// The hook sees *all* windows, so we need to make sure we operate on the right one.
_msg = msg;
_title = title;
// Set the hook.
// Suppress "GetCurrentThreadId() is deprecated" warning.
// It's documented that Thread.ManagedThreadId doesn't work with SetWindowsHookEx()
#pragma warning disable 0618
_hHook = Win32.SetWindowsHookEx(Win32.WH_CBT, _hookProcDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId());
#pragma warning restore 0618
// Pop a standard MessageBox. The hook will center it.
DialogResult rslt = MessageBox.Show(msg, title, btns, icon);
// Release hook, clean up (may have already occurred)
Unhook();
return rslt;
}
private static int HookCallback(int code, IntPtr wParam, IntPtr lParam)
{
int hHook = _hHook; // Local copy for CallNextHookEx() JIC we release _hHook
// Look for HCBT_ACTIVATE, *not* HCBT_CREATEWND:
// child controls haven't yet been created upon HCBT_CREATEWND.
if (code == Win32.HCBT_ACTIVATE)
{
string cls = Win32.GetClassName(wParam);
if (cls == "#32770") // MessageBoxes are Dialog boxes
{
string title = Win32.GetWindowText(wParam);
string msg = Win32.GetDlgItemText(wParam, 0xFFFF); // -1 aka IDC_STATIC
if ((title == _title) && (msg == _msg))
{
CenterWindowOnParent(wParam);
Unhook(); // Release hook - we've done what we needed
}
}
}
return Win32.CallNextHookEx(hHook, code, wParam, lParam);
}
_hook returns 0 / Last Error Code 87 (ERROR_INVALID_PARAMETER)
_hHook = Win32.SetWindowsHookEx(Win32.WH_CBT, _hookProcDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId());
int lastErrorCode = Marshal.GetLastWin32Error();
The SetWindowsHookEx:
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, WindowsHookProc lpfn, IntPtr hInstance, int threadId)
;
I ran the same code with .Net Framework 4.8 and it works fine! is there anything I have to update to make it works with .Net 6
In Windows you have a setting to "Show window contents while dragging". When this is off you are instead resizing with an outline of the window.
My WPF application has many controls on it, so it is extremely slow to resize. Is there a way to make my application resize by showing only the window outline instead of always updating the contents?
I found this question regarding WinForms but unfortunately I'm not able to adapt it to WPF. I can hook onto the HwndSource, but the message numbers may have changed in Windows 10, so the if statement in that answer is never entered... or there may be other things at work.
Also, inside the if it calls the WndProc base after is has changed a system parameter, then resetting the system parameter when it has finished calling it. But calling that method is not an option in WPF as a Window object does not have a way to forward the message.
public void OnViewLoaded() {
HwndSource source = HwndSource.FromHwnd(
new WindowInteropHelper(this).Handle);
source?.AddHook(WndProc);
}
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam,
IntPtr lParam, ref bool handled) {
if (msg == WM_SYSCOMMAND && (wParam.ToInt32() & 0xfff0) == SC_SIZE) {
// This if is never entered
int isDragFullWindow;
GetSystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, out isDragFullWindow, 0);
if (isDragFullWindow != 0)
SetSystemParametersInfo(SPI_SETDRAGFULLWINDOWS, 0, 0, 0);
// How to call this?
base.WndProc(ref m);
if (isDragFullWindow != 0)
SetSystemParametersInfo(SPI_SETDRAGFULLWINDOWS, 1, 0, 0);
}
}
In a few words: blocking Win up after Win + Tab makes Windows think Win is still down, so then pressing S with the Win key up for example will open the search charm rather than just type "s"... until the user presses Win again. Not blocking it means the Windows Start menu will show up. I'm in a conundrum!
I have no trouble hooking into shortcuts using Alt + Tab using LowLevelKeyboardHook, or Win + Some Ubounded Key using RegisterHotKey. The problem happens only with the Win key using LowLevelKeyboardHook.
In the example below, I'm taking over the Win up event when the Win + Tab combination is detected. This results in making every following keystrokes behave as if the Win key was still down.
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode != HC_ACTION)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
var keyInfo = (Kbdllhookstruct)Marshal.PtrToStructure(lParam, typeof(Kbdllhookstruct));
if (keyInfo.VkCode == VK_LWIN)
{
if (wParam == (IntPtr)WM_KEYDOWN) {
_isWinDown = true;
} else {
_isWinDown = false;
if (_isWinTabDetected) {
_isWinTabDetected = false;
return (IntPtr)1;
}
}
}
else if (keyInfo.VkCode == VK_TAB && _isWinDown) {
_isWinTabDetected = true;
if (wParam == (IntPtr)WM_KEYDOWN) {
return (IntPtr)1;
} else {
_isWinTabDetected = true;
Console.WriteLine("WIN + TAB Pressed");
return (IntPtr)1;
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
}
You can find the complete code here (note that it should replace your Program.cs in an empty WinForms project to run): https://gist.github.com/christianrondeau/bdd03a3dc32a7a718d62 - press Win + Tab and the Form title should update each time the shortcut is pressed.
Note that the intent of hooking into this specific combination is to provide an Alt + Tab alternative without replacing Alt + Tab itself. An answer providing the ability to launching custom code using Win + Tab will also be accepted.
Here are my ideas, for which I could not find documentation. All would potentially answer my question successfully.
Tell Windows to "cancel" the Win up without actually triggering it
Prevent Windows from launching the Start menu once
Hook directly in the Windows' Win + event rather than manually hooking into the keystrokes (This would be by far my first choice if that exists)
System need to know you release the Windows key.
I check the difference between my own hook who doesn't have this problem and the only diffence between your and mine is this line :
if (_isWinTabDetected) {
_isWinTabDetected = false;
return (IntPtr)1; //THIS LINE
}
This appears to do exactly what you want (omit RWin if you wish).
Please be considerate and unregister this KB hook when your app loses focus!
[DllImport("user32.dll")]
static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode == HC_ACTION)
{
var keyInfo = (Kbdllhookstruct) Marshal.PtrToStructure(lParam, typeof (Kbdllhookstruct));
if ((int) wParam == WM_KEYDOWN
&& keyInfo.VkCode == VK_TAB
&& (GetAsyncKeyState(Keys.LWin) < 0 || GetAsyncKeyState(Keys.RWin) < 0))
{
_mainForm.Text = "Win + Tab was pressed " + (++_winTabPressCounter) + " times";
return (IntPtr) 1;
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
I tried several things before discovering this technique.
This post was the most helpful https://stackoverflow.com/a/317550/55721
I have been trying to send mouse clicks to a WebBrowser control inside of my form using PostMessage(), and I have run into a rather significant issue. What I am trying to achieve is to simulate mouse clicks on this WebBrowser while my form is minimized. Usually PostMessage() would work just fine doing this, but it seems that it only works while my form has focus. This leads me to believe that there is some check going on to see if the particular website I am loading into my WebBrowser control is in focus before it handles mouse events.
This is how I send the clicks with my program:
private void SendClick(Point location)
{
resetHandle = true;
StringBuilder className = new StringBuilder(100);
while (className.ToString() != "Internet Explorer_Server")
{
handle = GetWindow(handle, 5); // 5 == child
GetClassName(handle, className, className.Capacity);
//MessageBox.Show(className.ToString());
}
IntPtr lParam = (IntPtr)((location.Y << 16) | location.X);
IntPtr wParam = IntPtr.Zero;
const uint downCode = 0x201;
const uint upCode = 0x202;
const uint moveCode = 0x200;
PostMessage(handle, moveCode, wParam, lParam); //move mouse
PostMessage(handle, downCode, wParam, lParam); // mousedown
PostMessage(handle, upCode, wParam, lParam); // mouseup
}
This is what the resetHandle does:
private void timer3_Tick(object sender, EventArgs e)
{
if (resetHandle == true)
{
handle = webBrowser1.Handle;
resetHandle = false;
}
}
I'm not sure if there is a better way of sending mouse events to a background window and I am open to any ideas. What I am really asking though is if it is at all possible to make a window believe it is in focus when it is actually still minimized?
Any help at all would be much appreciated!
Rather than keep the window minimized, keep it normal (restored), but set its X or Y coordinate so that it is positioned off screen.
If you want give the user the illusion of minimizing and restoring it, use HwndSource.AddHook to watch for SC_MINIMIZE. In your HwndSourceHook handler, move the window on or off screen according to the pseudo-minimized state, and set handled to true.
I am using the following code to get mouse messages on the current process.
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
For some reason when this code runs the mouse get slow for several seconds and then back to normal.
Any ideas?
Thanks
EDIT - hook method
private static IntPtr mouseEvent(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
{
MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
LastLeftClick = new ClickInfo { Time = DateTime.Now, X = hookStruct.pt.x, Y = hookStruct.pt.y };
}
return CallNextHookEx(hookID, nCode, wParam, lParam);
}
public class ClickInfo
{
public int X { get; set; }
public int Y { get; set; }
public DateTime Time { get; set; }
}
I had the same problem (only it's c++ project, not c#) and resolved it by changing hook from WH_MOUSE_LL to WH_MOUSE (from low-level to normal level). For WM_LBUTTONUP and WM_RBUTTONUP messages it works ok.
The thing that amuses me is that code with WH_MOUSE_LL was doing fine at the time I wrote it (no mouse freezes etc.) It seems like some security update for Windows changed the behavior of the mouse hooks and previously fine code become a problem.
I am sorry to follow up after such a long time but I have solved the issue by spawning a separate thread which handles the hook (I have not added everything to the code as it also translates messages but the main idea should be clear):
public Form1()
{
InitializeComponent();
Thread thread = new Thread(HookThread);
thread.IsBackground = true;
thread.Start();
}
private void HookThread()
{
_hookControl = new Control();
IntPtr handle = _hookControl.Handle;
_hookProc = new HookProc(HookFunction);
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
_hook = SetWindowsHookEx(HookType.WH_MOUSE_LL, _hookProc, GetModuleHandle(curModule.ModuleName), 0);// (uint)AppDomain.GetCurrentThreadId());
}
Application.Run();
UnhookWindowsHookEx(_hook);
_hook = IntPtr.Zero;
}
private IntPtr HookFunction(int code, IntPtr wParam, IntPtr lParam)
{
if (code < 0)
{
//you need to call CallNextHookEx without further processing
//and return the value returned by CallNextHookEx
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
int msg = wParam.ToInt32();
string messages = string.Join(", ", _messageMapping.Where(t => t.Item1 == msg).Select(t => t.Item2));
if (string.IsNullOrWhiteSpace(messages))
messages = msg.ToString();
Trace.WriteLine($"Messages: { messages }");
//return the value returned by CallNextHookEx
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
You can terminate the thread from any other thread by calling BeginInvoke on the created _hookControl:
_hookControl.BeginInvoke(((Action)(() => Application.ExitThread())));
What does your hook procedure look like?
If your process only has one UI thread, use a Message Filter instead:
http://msdn.microsoft.com/en-us/library/system.windows.forms.application.addmessagefilter.aspx
By setting a low level hook responsiveness of the mouse now becomes dependent on your main thread being responsive and a common mistake made is to set the hook early in the start-up process.
SetHook
LongRunningStartupProcess
Mouse isn’t responsive until here
During start-up it is best to dispatch the hook onto the thread so it happens at the end of the start-up process. Dispatcher.CurrentDispatcher.BeginInvoke(new Action(SetHook));
DispatchSetHook
LongRunningStartupProcess
SetHook(callback)
Mouse becomes responsive
This still has ongoing management issues within your application to ensure the main thread doesn’t do any long running processes as that will lock the mouse up too. This can easily be verified by setting the hook and then doing a Thread.Sleep on the main thread.
When you receive the hook event, turn off the book, then do your work, and if really still needed, put the hook back on.
This will stop the mouse from lagging.
Only stay hooked when you really need to.
Your hook proc is expensive; you just need to figure out why and how to fix it.
Even though though the code looks very minimal i suspect that there is some initial C# interop expense triggering the delays, perhaps due to JIT or paging.
If you change the code to do as much processing as possible off of this thread the problem should go away. as a C++ developer I even worry about the Marshal.PtrToStructure since low-level hooks are very sensitive and I can't say off the top of my head that this operation is guaranteed to be so cheap that it wouldn't impair mouse movement.
I've used low-level mouse hooks quite a bit in the past (in C++) and have never had problems unless the hook procedure itself is expensive. In C++ I try to avoid doing anything more than a PostMessage to an HWND that does the rest of the processing.