Global hotkey in console application

2019-01-03 02:53发布

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

4条回答
看我几分像从前
2楼-- · 2019-01-03 03:06

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();
    }
}
查看更多
放我归山
3楼-- · 2019-01-03 03:09

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
        }
    }
查看更多
叼着烟拽天下
4楼-- · 2019-01-03 03:11

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!");
    }
  }
}
查看更多
我命由我不由天
5楼-- · 2019-01-03 03:13

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

查看更多
登录 后发表回答