Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement keyboard handler cefSharp for shortcut keys

I've building a windows app for browsing web pages using cefSharp. I need to implement some short cut keys into that application, can any one tell me how can i achieve this functionality.

Ex.

ctrl + tab = move to next tab

I'm able to track if user presses any single key, but unable to track multi key pressing.

IKeyboardHandler

public class KeyboardHandler : IKeyboardHandler
{

    public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
    {
        bool result = false;
        Debug.WriteLine(String.Format("OnKeyEvent: KeyType: {0} 0x{1:X} Modifiers: {2}", type, windowsKeyCode, modifiers));
        // TODO: Handle MessageNeeded cases here somehow.
        return result;
    }

    public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
    {
        const int WM_SYSKEYDOWN = 0x104;
        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x101;
        const int WM_SYSKEYUP = 0x105;
        const int WM_CHAR = 0x102;
        const int WM_SYSCHAR = 0x106;
        const int VK_TAB = 0x9;

        bool result = false;

        isKeyboardShortcut = false;

        // Don't deal with TABs by default:
        // TODO: Are there any additional ones we need to be careful of?
        // i.e. Escape, Return, etc...?
        if (windowsKeyCode == VK_TAB)
        {
            return result;
        }

        Control control = browserControl as Control;
        int msgType = 0;
        switch (type)
        {
            case KeyType.RawKeyDown:
                if (isSystemKey)
                {
                    msgType = WM_SYSKEYDOWN;
                }
                else
                {
                    msgType = WM_KEYDOWN;
                }
                break;
            case KeyType.KeyUp:
                if (isSystemKey)
                {
                    msgType = WM_SYSKEYUP;
                }
                else
                {
                    msgType = WM_KEYUP;
                }
                break;
            case KeyType.Char:
                if (isSystemKey)
                {
                    msgType = WM_SYSCHAR;
                }
                else
                {
                    msgType = WM_CHAR;
                }
                break;
            default:
                Trace.Assert(false);
                break;
        }
        // We have to adapt from CEF's UI thread message loop to our fronting WinForm control here.
        // So, we have to make some calls that Application.Run usually ends up handling for us:
        PreProcessControlState state = PreProcessControlState.MessageNotNeeded;
        // We can't use BeginInvoke here, because we need the results for the return value
        // and isKeyboardShortcut. In theory this shouldn't deadlock, because
        // atm this is the only synchronous operation between the two threads.
        control.Invoke(new Action(() =>
        {
            Message msg = new Message() { HWnd = control.Handle, Msg = msgType, WParam = new IntPtr(windowsKeyCode), LParam = new IntPtr(nativeKeyCode) };

            // First comes Application.AddMessageFilter related processing:
            // 99.9% of the time in WinForms this doesn't do anything interesting.
            bool processed = Application.FilterMessage(ref msg);
            if (processed)
            {
                state = PreProcessControlState.MessageProcessed;
            }
            else
            {
                // Next we see if our control (or one of its parents)
                // wants first crack at the message via several possible Control methods.
                // This includes things like Mnemonics/Accelerators/Menu Shortcuts/etc...
                state = control.PreProcessControlMessage(ref msg);
            }
        }));
        if (state == PreProcessControlState.MessageNeeded)
        {
            // TODO: Determine how to track MessageNeeded for OnKeyEvent.
            isKeyboardShortcut = true;
        }
        else if (state == PreProcessControlState.MessageProcessed)
        {
            // Most of the interesting cases get processed by PreProcessControlMessage.
            result = true;
        }
        Debug.WriteLine(String.Format("OnPreKeyEvent: KeyType: {0} 0x{1:X} Modifiers: {2}", type, windowsKeyCode, modifiers));
        Debug.WriteLine(String.Format("OnPreKeyEvent PreProcessControlState: {0}", state));
        return result;
    }
like image 789
Ashish Rathore Avatar asked Mar 02 '16 14:03

Ashish Rathore


1 Answers

Finally got an alternative to implement shortcut functionality without implementing IKeyboardHandler.

Here i used Global Keyboard Hook to implement this.

GlobalKeyboardHook

public class GlobalKeyboardHook
{
    #region Variables and dll import
    #region For key capturing
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr hhk, int code, int wParam, ref keyBoardHookStruct lParam);
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, LLKeyboardHook callback, IntPtr hInstance, uint theardID);
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);

    public delegate int LLKeyboardHook(int Code, int wParam, ref keyBoardHookStruct lParam);

    public struct keyBoardHookStruct
    {
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
        public int dwExtraInfo;
    }

    const int WH_KEYBOARD_LL = 13;
    const int WM_KEYDOWN = 0x0100;
    const int WM_KEYUP = 0x0101;
    const int WM_SYSKEYDOWN = 0x0104;
    const int WM_SYSKEYUP = 0x0105;

    LLKeyboardHook llkh;
    public List<Keys> HookedKeys = new List<Keys>();
    IntPtr Hook = IntPtr.Zero;

    public event KeyEventHandler KeyDown;
    public event KeyEventHandler KeyUp;
    #endregion

    #region For modifier capturing
    /// <summary> 
    /// Gets the state of modifier keys for a given keycode. 
    /// </summary> 
    /// <param name="keyCode">The keyCode</param> 
    /// <returns></returns> 
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
    public static extern short GetKeyState(int keyCode);

    //Modifier key vkCode constants 
    private const int VK_SHIFT = 0x10;
    private const int VK_CONTROL = 0x11;
    private const int VK_MENU = 0x12;
    private const int VK_CAPITAL = 0x14;
    #endregion 
    #endregion

    #region Constructor
    // This is the Constructor. This is the code that runs every time you create a new GlobalKeyboardHook object
    public GlobalKeyboardHook()
    {
        llkh = new LLKeyboardHook(HookProc);
        // This starts the hook. You can leave this as comment and you have to start it manually 
        // Or delete the comment mark and your hook will start automatically when your program starts (because a new GlobalKeyboardHook object is created)
        // That's why there are duplicates, because you start it twice! I'm sorry, I haven't noticed this...
        // hook(); <-- Choose!
    }
    ~GlobalKeyboardHook()
    { unhook(); }
    #endregion 

    #region Functions and implementation
    /// <summary>
    /// Hook (Start listening keybord events)
    /// </summary>
    public void hook()
    {
        IntPtr hInstance = LoadLibrary("User32");
        Hook = SetWindowsHookEx(WH_KEYBOARD_LL, llkh, hInstance, 0);
    }
    /// <summary>
    /// Unhook (Stop listening keybord events)
    /// </summary>
    public void unhook()
    {
        UnhookWindowsHookEx(Hook);
    }
    /// <summary>
    /// Pass key into event
    /// </summary>
    /// <param name="Code">Key code</param>
    /// <param name="wParam">int event type (keydown/keyup)</param>
    /// <param name="lParam">keyBoardHookStruct enum for detecting key</param>
    /// <returns>next hook call</returns>
    public int HookProc(int Code, int wParam, ref keyBoardHookStruct lParam)
    {
        if (Code >= 0)
        {
            Keys key = (Keys)lParam.vkCode;
            if (HookedKeys.Contains(key))
            {
                //Get modifiers 
                key = AddModifiers(key);
                KeyEventArgs kArg = new KeyEventArgs(key);
                if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
                {
                    KeyDown(this, kArg);
                }
                else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null))
                {
                    KeyUp(this, kArg);
                }
                if (kArg.Handled)
                    return 1;
            }
        }
        return CallNextHookEx(Hook, Code, wParam, ref lParam);
    }
    /// <summary> 
    /// Checks whether Alt, Shift, Control or CapsLock 
    /// is pressed at the same time as the hooked key. 
    /// Modifies the keyCode to include the pressed keys. 
    /// </summary> 
    private Keys AddModifiers(Keys key)
    {
        //CapsLock 
        if ((GetKeyState(VK_CAPITAL) & 0x0001) != 0) key = key | Keys.CapsLock;
        //Shift 
        if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) key = key | Keys.Shift;
        //Ctrl 
        if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) key = key | Keys.Control;
        //Alt 
        if ((GetKeyState(VK_MENU) & 0x8000) != 0) key = key | Keys.Alt;
        return key;
    }
    #endregion 
}

Start hook

To start hook add below code to form load or application start
//Start listening keybord events
gHook = new GlobalKeyboardHook();
gHook.KeyDown += new KeyEventHandler(gHook_KeyDown);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
{
   gHook.HookedKeys.Add(key);
}
gHook.hook();

Stop hook

Add this code on application exit(form closing event)    
//Stop listening keybord events
gHook.unhook();

Handel key down event for shortcut implementation

    public void gHook_KeyDown(object sender, KeyEventArgs e)
    {
        if (this.ContainsFocus)
        {
            if (e.KeyData.ToString().ToUpper().IndexOf("ALT".ToUpper()) >= 0
               && e.KeyValue == 66)//B = 66
            {
                //ALT + B 
                new Thread(() =>
                {
                    // execute this on the gui thread. (winforms)
                    this.Invoke(new Action(delegate
                    {
                        tosBrowserBtnBack_Click(this, new EventArgs());
                    }));

                }).Start();
            }
            else if (e.KeyData.ToString().ToUpper().IndexOf("ALT".ToUpper()) >= 0
              && e.KeyValue == 70)//F = 70
            {
                //ALT + F
                new Thread(() =>
                {
                    // execute this on the gui thread. (winforms)
                    this.Invoke(new Action(delegate
                    {
                        tosBrowserBtnFor_Click(this, new EventArgs());
                    }));

                }).Start();
            }
    }

Global hook works for every keystroke whether your application has focus or not, so here I've used this.ContainsFocus to check current form has focus or not, if has then handle shortcut events. If you need to implementation shortcut on hole application then you need to check if application has focus or not. For that you need to create a new class

ApplicationFocus

public class ApplicationFocus
{
    /// <summary>Returns true if the current application has focus, false otherwise</summary>
    public static bool ApplicationIsActivated()
    {
        var activatedHandle = GetForegroundWindow();
        if (activatedHandle == IntPtr.Zero)
        {
            return false;       // No window is currently activated
        }

        var procId = Process.GetCurrentProcess().Id;
        int activeProcId;
        GetWindowThreadProcessId(activatedHandle, out activeProcId);

        return activeProcId == procId;
    }


    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
}

Then replace if (this.ContainsFocus) with ApplicationFocus.ApplicationIsActivated() in gHook_KeyDown event.

This is worked for me.

like image 84
Ashish Rathore Avatar answered Nov 04 '22 10:11

Ashish Rathore