How to Hook into Hardware Keyboard Events at an Application Level

Is it possible to hook into Hardware keyboard events at an Application level?

You can use the KeyCommands to track the key pressing from hardware keyboard.

keyCommands

A responder object that supports hardware keyboard commands can redefine this property and use it to return an array of UIKeyCommand objects that it supports. Each key command object represents the keyboard sequence to recognize and the action method of the responder to call in response.

The key commands you return from this method are applied to the entire responder chain. When an key combination is pressed that matches a key command object, UIKit walks the responder chain looking for an object that implements the corresponding action method. It calls that method on the first object it finds and then stops processing the event.

In Xamarin.forms, you have to create custom renderer for ContentPage at iOS platform. Then the keycommands can be added in that page renderer.

If you want the key presses can be handled by the ViewController(Page), not just the input controls(such as Entry), use canBecomeFirstResponder to enable viewcontroller to become a first responder.

For example, the custom renderer of ContentPage in iOS platform could like this:

using System;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using KeyCommandsInXamarinForms.iOS;

[assembly: ExportRenderer(typeof(ContentPage), typeof(MyCustomPageRenderer))]
namespace KeyCommandsInXamarinForms.iOS
{
public class MyCustomPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);

if (e.OldElement != null || Element == null)
{
return;
}

//Create your keycommand as you need.
UIKeyCommand keyCommand1 = UIKeyCommand.Create(new NSString("1"), UIKeyModifierFlags.Command, new ObjCRuntime.Selector("Action:"));
UIKeyCommand keyCommand2 = UIKeyCommand.Create(new NSString("\t"), 0, new ObjCRuntime.Selector("Action:"));
//Add your keycommands
this.AddKeyCommand(keyCommand1);
this.AddKeyCommand(keyCommand2);
}

[Export("Action:")]
private void Excute(UIKeyCommand keyCommand)
{
Console.WriteLine(String.Format("key pressed - {0}", keyCommand.Value);
}

//Enable viewcontroller to become the first responder, so it is able to respond to the key commands.
public override bool CanBecomeFirstResponder
{
get
{
return true;
}
}
}
}

Notice this line, using typeof(ContentPage) as the handler parameter, then you don't need to change anything in your PCL:

[assembly: ExportRenderer(typeof(ContentPage), typeof(MyCustomPageRenderer))]

How to send hardware level keyboard input by program?

I once used SendInput to control a game character. Game (icy tower?) was using directx input system (I think?) and somehow it was ignoring keybd_event calls but this method worked. I do not know how close to hardware you need to be but did this the trick for me. I used virtual key codes but turned them into scancodes for this answer.

UINT PressKeyScan(WORD scanCode)
{
INPUT input[1] = {0};
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = NULL;
input[0].ki.wScan = scanCode;
input[0].ki.dwFlags = KEYEVENTF_SCANCODE;

UINT ret = SendInput(1, input, sizeof(INPUT));

return ret;
}

UINT ReleaseKeyScan(WORD scanCode)
{
INPUT input[1] = {0};
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = NULL;
input[0].ki.wScan = scanCode;
input[0].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;

UINT ret = SendInput(1, input, sizeof(INPUT));

return ret;
}

To simulate press and release you use them sequentially (or you may create a separate function for press and release that use same INPUT structure).

WORD scanCodeSpace = 0x39;
PressKeyScan(scanCodeSpace);
ReleaseKeyScan(scanCodeSpace)

You can use MapVirtualKeyA to get scan code from virtual key code.

Multiple keyboards and low-level hooks

Yes I stand corrected, my bad, learning something new every day.

Here's my attempt at making up for it :) :

  • Register the devices you want to use for raw input (the two keyboards) with ::RegisterRawInputDevices().

  • You can get these devices from GetRawInputDeviceList()

  • After you've registered your devices, you will start getting WM_INPUT messages.

  • The lParam of the WM_INPUT message contains a RAWKEYBOARD structure that you can use to determine the keyboard where the input came from, plus the virtual keycode and the type of message (WM_KEYDOWN, WM_KEYUP, ...)

  • So you can set a flag of where the last message came from and then dispatch it to the regular keyboard input handlers.

Global keyboard capture in C# application

Stephen Toub wrote a great article on implementing global keyboard hooks in C#:

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;

public static void Main()
{
_hookID = SetHook(_proc);
Application.Run();
UnhookWindowsHookEx(_hookID);
}

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)
{
int vkCode = Marshal.ReadInt32(lParam);
Console.WriteLine((Keys)vkCode);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

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

Low-level keyboard hook: callback not reached

The issue is that your console application doesn't have a message loop to process the hook messages.

Replace your while(1) loop with the following and see if that works:

   MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) != 0 )
{
TranslateMessage( &msg );
DispatchMessage( &msg );

if(gotKey)
printf("Got Key!\n");

gotKey = FALSE;
}


Related Topics



Leave a reply



Submit