Cannot detect global mouse down with MouseKeyHook

Viewed 617

I've been researching for the past few hours to figure out how to detect mouse left clicks globally (not focused) and stumbled upon multiple posts mentioning to use globalmousekeyhook which I installed via NuGet which I have implemented as seen below:

using Gma.System.MouseKeyHook;
using System;

namespace mouse_hook_test
{
    class Program
    {
        static private IKeyboardMouseEvents m_GlobalHook;

        static void Main(string[] args)
        {
            Subscribe();

            Console.ReadKey();
        }

        static public void Subscribe()
        {
            m_GlobalHook = Hook.GlobalEvents();

            m_GlobalHook.MouseDownExt += GlobalHookMouseDownExt;
        }

        static private void GlobalHookMouseDownExt(object sender, MouseEventExtArgs e)
        {
            Console.WriteLine("Mouse Click.");
        }
    }
}

When I run it, my on screen mouse suddenly starts lagging to the point where it's unusable (I even struggled to close the command prompt) and I have also tried clicking both left and right but nothing get's logged. Am I doing something completely stupid? I feel there's something really obvious I'm missing because no one else is having this issue, or if there's another solution I'd be glad to try it. Thank you.

Visual Studio 2019, Windows 10 Pro, Project: .NET Framework 4.7.2, Console Application, ran with debug mode

2 Answers

I was actually playing around as per usual, looking for different ways to do it and then stumbled upon the Windows API function for getting states of keys (GetKeyStates) (including the mouse), so I imported the user32 DLL and started a loop that checks the left mouse button state (0x01) every 50ms (found that to be the best time) and threw it in a thread so it wouldn't block the main one.

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace mouse_hook_test
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(detect);
            thread.Start();
        }

        // Import the DLL that contains the function and grab it.
        [DllImport("user32.dll")]
        public static extern short GetKeyState(int vKey);

        // A very simple implementation for tracking the state of the left mouse button.
        static void detect()
        {
            short oldState = GetKeyState(0x01);

            while (true)
            {
                short newState = GetKeyState(0x01);                

                if (oldState != newState)
                {
                    oldState = newState;

                    if (newState < 0)
                    {
                        onMouseClick();
                    }
                }

                Thread.Sleep(50);
            }
        }

        static void onMouseClick()
        {
            Console.WriteLine("Clicked!");
        }
    }
}

There is probably a better solution for this (which I couldn't find) and so why I am using this. I originally based this of an answer to my exact question, but instead it was written in Python so I converted it to C#. I was getting sick of seeing the only answer to my question was to 'use some 3rd party library' that doesn't properly work for me for whatever reason and have also lowered memory usage from 30mb to 7mb :).

I have also tested it on Windows 7 and it works marvellous.

The reason is because the messages are posted by the global hook to the thread's Win32 message queue. This is the same queue used to pass messages to WndProc in a Form.

To receive messages, you must pump the message queue with Application.Run(). But in a console app, there is no Application.Run().

There are various solutions give already for this, however I think the following is probably the best.

using Gma.System.MouseKeyHook;
using System;
using System.Windows.Forms;

namespace mouse_hook_test
{
    class Program
    {
        static private IKeyboardMouseEvents m_GlobalHook;

        static void Main(string[] args)
        {
            //Run pump on another background thread
            //Task.Run feels like a bad idea without a pump/synchronisation context
            var pumpThread = new Thread(PumpQueue){IsBackground = true};
            pumpThread.Start();

            Console.ReadKey();
            Application.Exit();     // closes down the pump
        }

        [STAThread]      // I think this is necessary to ensure thread-safety
        static PumpQueue()
        {
            Subscribe();

            Application.Run();
        }

        static public void Subscribe()
        {
            m_GlobalHook = Hook.GlobalEvents();

            m_GlobalHook.MouseDownExt += GlobalHookMouseDownExt;
        }

        static private void GlobalHookMouseDownExt(object sender, MouseEventExtArgs e)
        {
            Console.WriteLine("Mouse Click.");
        }
    }
}
Related