B4J Library jNativeHookB4J for intercepting system input events

Discussion in 'B4J Libraries & Classes' started by Roycefer, Jul 4, 2015.

  1. Roycefer

    Roycefer Well-Known Member Licensed User

    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:
    Code:
    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.
     

    Attached Files:

    Last edited: Jul 6, 2015
  2. Roycefer

    Roycefer Well-Known Member Licensed User

    The obvious evil uses aside, the chief use of this library, in my opinion, is the ability to have your programs react to special key combinations (like "ctrl_f", for example) that were previously unavailable solely through JavaFX's keyboard event system. Additionally, it doesn't matter what JavaFX Node is currently in focus in your program, the user can enter a special key combo and your program can observe it and react accordingly. Furthermore, this library could enable the writing of full screen games that capture mouse and keyboard input while being played similar to how native games behave.
     
  3. thader2012

    thader2012 Member Licensed User

    Thank you for this library.:)
     
  4. thader2012

    thader2012 Member Licensed User

    Example does not work.
    Code:
    java.lang.UnsupportedClassVersionError: butt/droid/nativeHook/JavaNativeKeyEvent : Unsupported major.minor version 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:
    791)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:
    142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:
    449)
        at java.net.URLClassLoader.access$
    100(URLClassLoader.java:71)
        at java.net.URLClassLoader$
    1.run(URLClassLoader.java:361)
        at java.net.URLClassLoader$
    1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:
    354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:
    423)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:
    308)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:
    356)
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:
    2451)
        at java.lang.Class.getMethod0(Class.java:
    2694)
        at java.lang.Class.getMethod(Class.java:
    1622)
        at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:
    494)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:
    486)
    Exception in thread "main"
     
  5. Roycefer

    Roycefer Well-Known Member Licensed User

    What Java version are you using?
     
  6. billzhan

    billzhan Active Member Licensed User

    Same error.

    win7 64bit , B4J 2.8 , jdk 7u71
     
  7. Roycefer

    Roycefer Well-Known Member Licensed User

    I compiled the library against jdk 8u40. Is there any special reason you're still using jdk 7? Note that Oracle has stopped updating jdk 7 and they recommend moving to jdk 8. If there's enough demand for it, I'll compile a separate version for jdk 7 but I think if there's nothing special keeping you tied to jdk 7, you should upgrade.

    I just corrected a few mistakes I had in the example code. What is posted now should run, provided you are on jdk 8.
     
  8. Roycefer

    Roycefer Well-Known Member Licensed User

    I just uploaded an alternative version called jNativeHookB4JAncient that was compiled against jdk 6u26. I've tested it on jdk 7u71 and it works (whereas the normal library didn't).
     
  9. billzhan

    billzhan Active Member Licensed User

    Roycefer,


    jNativeHookB4JAncient works well (win7 64bit , B4J 2.8 , jdk 7u71)
    jNativeHookB4J works well (win8.1 32bit B4J 3.0 jdk 8u45). Need to turn off windows defender before running.

    Java 7u71 is used for ongoing programme, had trouble with Java8.

    Thanks
     
  10. Roycefer

    Roycefer Well-Known Member Licensed User

    Oh yes, I forgot to mention, if your antivirus is worth the paper on which it's written, it will occasionally try to delete or quarantine apps written with this library, for obvious reasons. I've even had one interfere with compilation. You should be able to tell your antivirus to ignore certain apps, though. In fact, if your antivirus isn't occasionally bothered by this library, you should rethink your antivirus lifestyle choices.
     
  11. thader2012

    thader2012 Member Licensed User

    i am working with java 1.8.0_31-b13
     
  12. Roycefer

    Roycefer Well-Known Member Licensed User

    Try the new example code in the original post with the standard jNativeHookB4J library. If it doesn't work, try it again with the jNativeHookB4JAncient library (attached to the original post). I just tested both libraries with jdk 8u31 and they both work on my Windows 7 64-bit machine.

    Also, make sure your antivirus isn't blocking it. See posts #9 and #10.
     
  13. thader2012

    thader2012 Member Licensed User

    Ok thanks, with your instructions, it works now.;)
     
  14. Erel

    Erel Administrator Staff Member Licensed User

    I think that it is better to delegate the event to the main thread in the library. This is what the developers will expect.

    You can add a second more advanced option if there is a real use case for that, that will directly raise the event.

    It is simple to delegate the event with ba.raiseEventFromDifferentThread.
     
  15. thader2012

    thader2012 Member Licensed User

    One question, as we differentiate one letter in uppercase or lowercase . Nke.keyCode returns the same letter code . Thanks for your tips.
     
  16. Roycefer

    Roycefer Well-Known Member Licensed User

    Erel:
    The underlying library, JNativeHook, has the capability to consume system input events. I hope to extend jNativeHookB4J to wrap these capabilities in the future. However, I don't think developers will be able to consume system events from within the event subs if the event subs aren't executing in the JNativeHook delegate thread. I could probably add an option that would allow event subs to run in the main thread, though. I'll look into it.

    thader2012:
    Yes, this library is reporting the raw input events, not the intent of the user. It basically tells you what key was pressed, not what letter was "entered". The latter is determined by the context in which the user pressed the key. If you want to know if the user was holding down Shift when pressing a letter, though, you can create a Boolean variable called ShiftDown and set it to True whenever NativeKeyPressed reports Shift was pressed and set it to False whenever NativeKeyReleased reports Shift was released. Then, whenever a letter key is pressed, you'll know if Shift was being held down at the time. Remember, you're dealing almost directly with the hardware, here. There's a lot less abstraction than what we are used to in JavaFX.
     
  17. thader2012

    thader2012 Member Licensed User

    thanks for your quick answer
     
  18. Roycefer

    Roycefer Well-Known Member Licensed User

    jNativeHookB4J and jNativeHookB4JAncient have been updated to version 1.1. The primary change is that now most event subs return a Boolean value. If you have event consumption enabled, you can return True in your event sub to consume the event and prevent it from propagating through the system to other applications, including the application that is currently in focus. This is quite a powerful ability. You could, for example, set up a window such that if a MouseMoved event occurs outside of that window, the event is consumed. This prevents the user from moving the mouse outside the window, just like in a lot of native games.

    Due to limitations in the underlying native library, event consumption only works on Windows and Mac machines and not on X11-based platforms (Unix, Linux, Solaris).

    My experiments reveal that subs that return a Boolean in their signature will return a False if no Return statement is executed in the sub. Additionally, such subs will still compile, even though the B4J IDE will issue warnings. This means that all your old code (that you've developed over the past day) won't break with this update.

    See the updated example code in the original post for an example of event consumption in operation.
     
    Last edited: Jul 5, 2015
  19. thader2012

    thader2012 Member Licensed User

    Thank you for version 1.1
     
  20. Suntzu

    Suntzu Member Licensed User

    Roycefer,

    Does this support UI app?

    I am getting the below error. My setup:

    B4J 3.61
    Java 1.8.0_51
    JNativeHook v2.0.2
    Tried on both jNativeHookB4J1.1 and jNativeHookB4JAncient1.1

    Any idea? Thank you in advance.

    Code:
    Program started.
    Main._appstart (java line: 
    633)
    java.lang.UnsatisfiedLinkError: Write error
        at org.jnativehook.GlobalScreen.<clinit>(Unknown Source)
        at butt.droid.nativeHook.NativeHook.Initialize(NativeHook.java:
    61)
        at XXX.XXX.XXX.main._appstart(main.java:
    633)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:
    62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
    43)
        at java.lang.reflect.Method.invoke(Method.java:
    497)
        at anywheresoftware.b4a.BA.raiseEvent2(BA.java:
    93)
        at anywheresoftware.b4a.BA.raiseEvent(BA.java:
    84)
        at cloyd.lexmark.support.main.start(main.java:
    36)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$
    159(LauncherImpl.java:863)
        at com.sun.javafx.application.LauncherImpl$$Lambda$
    54/5206331.run(Unknown Source)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$
    172(PlatformImpl.java:326)
        at com.sun.javafx.application.PlatformImpl$$Lambda$
    47/3711894.run(Unknown Source)
        at com.sun.javafx.application.PlatformImpl.lambda$
    null$170(PlatformImpl.java:295)
        at com.sun.javafx.application.PlatformImpl$$Lambda$
    49/20235569.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$
    171(PlatformImpl.java:294)
        at com.sun.javafx.application.PlatformImpl$$Lambda$
    48/7816767.run(Unknown Source)
        at com.sun.glass.ui.InvokeLaterDispatcher$
    Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$
    null$145(WinApplication.java:101)
        at com.sun.glass.ui.win.WinApplication$$Lambda$
    37/11714529.run(Unknown Source)
        at java.lang.Thread.run(
    Thread.java:745)
     
Loading...