B4J Library jNativeHookB4J for intercepting system input events

Generally speaking, a hook is a method of intercepting program events without a dedicated API. You can read more about it here: https://en.wikipedia.org/wiki/Hooking

This library wraps the JNativeHook library, allowing you to intercept global keyboard events and mouse events. That is, your B4J application can now recieve these events even if they occur outside your application or your application is not in focus or has no GUI. In order to use this library, download the JNativeHook-<version>.zip file from this address: https://github.com/kwhat/jnativehook/releases . The B4J library was written for version 2.0.2. Unzip the archive and place the JNativeHook.jar file in your B4J external libraries folder. Then download the jNativeHookB4J.zip file attached to this post and extract the jNativeHookB4J.jar and jNativeHookB4J.xml files to your B4J external libraries folder.

The following code shows an example of its usage:
B4X:
Sub Process_Globals
    Dim NH as NativeHook
    Dim t as Timer
End Sub

Sub AppStart(Args() as String)
    NH.Initialize("NH", Me)        'Initializes and registers the NativeHook
    NH.startNativeKeyListener    'starts a NativeKeyListener
    NH.enableKillCode   'Allows you to manually unregister by pressing Windows_Escape
    NH.EnableEventConsumption  'Allows you to consume input events
    t.Initialize("t", 60*1000)
    t.Enabled = True            'Timer will unregister the NativeHook in 1 minute
    StartMessageLoop
End Sub

'Event fired whenever a key is pressed, regardless of what application is in focus
Sub NH_NativeKeyPressed(nke as NativeKeyEvent) As Boolean
    If NH.isCharacter(nke.keyCode) Then
        Log(nke.keyText & " Pressed")
        If nke.keyText=="F" Then Return True    'Consumes the event if the user pressed the "F" key
    End If
    Return False
End Sub

'Event fired when the NativeHook is unregistered
Sub NH_Unregistered
    Log("NativeHook unregistered")
    StopMessageLoop    'No more event loops means this program ends here
End Sub

Sub t_Tick
    t.Enabled = False
    NH.unregisterNativeHook
End Sub

Note how in the above example, all the NH_ events appear in the Main module. This doesn't mean, however, that they will execute in the Main thread. They will execute in the JNativeHook delegate thread. This is a general advantage however it means that if you want to access UI elements from within your event subs, you will have to use Thread.RunOnGuiThread() to do so. Also note that unregistering the NativeHook will end the NativeHook event loop. If this was the only active event loop in your program (no UI event loop, no server event loop, etc...), ending that event loop will end your program. That is why an _Unregistered event is provided.

Make sure to unregister the NativeHook when your program is done using it. If too many NativeHooks are left registered but unused, this will interfere with your ability to create new instances of NativeHook and for other programs that respond to these system events to do so. If this happens, reboot your computer and it should clear all those dangling NativeHooks from memory. However, if you properly unregister your NativeHooks, you shouldn't have any problems.

My preliminary tests with this library indicate that it performs very well and consumes very little computing resources. On a few years old i7 system, a program that I wrote with this library stayed at around 0.03% CPU load and very rarely spiked to 0.5% even with heavy mouse and keyboard usage and all native listeners running. The B4J library is pretty heavily commented and the comments sort of lead you through its usage. The JNativeHook.jar file that you download from github has packed into it all the native files necessary to work on Windows, Linux and Darwin-based Mac systems. I've only tested it on Windows, however.

EDIT (4JUL2015): jNativeHookB4J.zip was compiled against jdk 8u40. I've attached another .zip called jNativeHookB4JAncient.zip that was compiled against jdk 6u26 which should work for users and customers still using Javas 6 or 7 (I've only tested Ancient on jdk 7u71, though). jNativeHookB4jAncient works in exactly the same way except the NativeHook object is called NativeHookAncient. If you're using Java 5 or below, well, you're on your own...

EDIT (5JUL2015): Updated both libraries to version 1.1 to enable event consumption. See post #18 for details.
 

Attachments

  • jNativeHookB4J1.1.zip
    8.6 KB · Views: 850
  • jNativeHookB4JAncient1.1.zip
    8.7 KB · Views: 655
Last edited:

Cableguy

Expert
Licensed User
Actually no, just wanted to report that particular behaviour, that the hook does not work when used inside a class. Later I can post a small example if you have the time to look at it.
In my case I just changed my code a bit and set the hook in Main, so I don't stay stuck on this.
 

Roycefer

Well-Known Member
Licensed User
Typically one posts a stack trace and the code that generated that stack trace when reporting errors.
 

Cableguy

Expert
Licensed User
Since the thread was 3 years old, I wasn't expecting much attention...
As I said, I will post all relevant details along with a sample project that shows this behaviour later today, when I get back from my day job
 

Douglas Farias

Expert
Licensed User
Hi @Roycefer
i m testing your lib, and it is working ok.

i found only one problem and i dont know how to fix it.
when i run this line
B4X:
NH.startNativeKeyListener
i get the logs of the pressed keys.

but i cant use special characters like â ã etc.....

on this sub i use return false.
B4X:
Sub NH_NativeKeyPressed(nke As NativeKeyEvent) As Boolean
    Log(nke.KeyText)
    Return False
End Sub

true or false = cant use special characters.
how can i set the listener to listen special characters?

the problem is not the log, the real problem is when i start the listener i cant use special characters on any program, out of b4j code.

For example here: a a a << i tried to use special characters, but the b4j is runing and i cant use, i will stop the debug. á â ã now it works :). the listener are blocking like a return true.

thx
 
Last edited:

ThRuST

Well-Known Member
Licensed User
@Douglas Farias I assume also @ and [] chars doesn't work either, correct? And I also suspect that the special chars issue is only related to PC, since I had the same problem on PC but not on Mac, probably because of when running vmWare it uses another keyboard mapping so you might want to check that out and confirm it with a reply. I'm not able to provide any other answer than this, but you can try to detect which key is pressed and then call the ASCII value for it. Not the nicest of solutions, but that might work on PC that is.
 

Roycefer

Well-Known Member
Licensed User
Does your keyboard have physical keys for those special characters? Or are you pressing a special key combo to get those special characters? The NativeHook doesn't listen for the user's "intent". It listens for only what keys were pressed.

Do you have event consumption enabled? If so, try turning that off.
 

ThRuST

Well-Known Member
Licensed User
I have noticed that ALT is set out of play. Would be good if someone else can confirm this to trace the cause of this. It's not a big deal but annoying in a way. I will do some tests myself to try to locate the problem when I have the time for it.
 

Douglas Farias

Expert
Licensed User
Does your keyboard have physical keys for those special characters? Or are you pressing a special key combo to get those special characters? The NativeHook doesn't listen for the user's "intent". It listens for only what keys were pressed.

Do you have event consumption enabled? If so, try turning that off.

Hi

yes, on brazil special characters are on all keyboards.
WhatsApp Image 2019-04-04 at 10.47.34.jpeg

the event consumption is off (and return false too).
when i press the special characters i can see the log of the keyname and keycode, but is like it have a return true internal only to special keys, all another keys works fine.


'HERE IS THEN

B4X:
'Non-UI application (console / server application)
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
#End Region

Sub Process_Globals
    Private NH As NativeHook
    Private salvaTextos As String
End Sub

Sub AppStart (Args() As String)
    NH.Initialize("NH", Me)
    NH.startNativeKeyListener
    StartMessageLoop
End Sub

Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub NH_NativeKeyPressed(nke As NativeKeyEvent) As Boolean
    salvaTextos = salvaTextos & nke.KeyText
    Log(nke.KeyText)
    Return False
End Sub


Sub NH_Unregistered
    StopMessageLoop
End Sub
Here is what i m using.

i m runing this in debug now, lets try use special key.
a a a a
dont works here on the forum...
i will turn stop the debug.
ã ~ â ^ works.
something internal is blocking this special characters (like a return true on special characters).

on the logs i can see the name of the special characters

Waiting for debugger to connect...
Program started.
A
B
C
D
E
F
Aspas
Crase
Ponto Final
Vírgula
Barra
Indefinido

i will make anothers tests to find a solution.
if you have another sugestion pls send me.

thx
 

Douglas Farias

Expert
Licensed User
can you the not just use
B4X:
if nke.KeyText = "Aspas" then return true
?
Hi @DonManfred
i can see the log "Aspas" but it dont works on any place if the example above is runing.


for example here, on the forum text editor.
the example is runing on debug.
if i press Aspas here is the result: a a a a
if i use
B4X:
    If nke.KeyText = "Aspas" Then
        Log("here")
        Return True
    End If
i can see the log 'here' but i still cant use Aspas on any other place.
if i set it to false i still cant use Aspas.

now if o stop the debug. i can use normal. here : ã ~ â ã ~
the problem is on

B4X:
NH.startNativeKeyListener
:(:(






Edit: hmmm i found this.
https://github.com/kwhat/jnativehook/issues/213

maybe the error is on JNativeHook jar, and not on the b4j lib. i will try another version...
 
Last edited:

Roycefer

Well-Known Member
Licensed User
I'm pretty sure I mentioned this in an earlier post but I'll repeat it here. You should not used this library in debug mode. B4X debug mode runs everything in the main thread and that's contrary to this library's threading model. Try it in release mode.

The event subs are supposed to run in their own special thread.
 
Top