📜 ⬆️ ⬇️

Register global keystrokes using JNA

Hello, in this article I will tell you how to register global keystrokes from Java under Windows, Linux, BSD and Mac OSX using the excellent JNA library.

What is JNA for?


Java is difficult with the desktop; for some things you need to write bridges to interact with the operating system. One of these functionalities is global hotkeys, which are very popular in audio players, when even in the hidden state the program can be controlled using certain keyboard shortcuts or media buttons. JNA comes to the rescue - add-on jni and libffi to call native libraries, it supports almost all popular platforms, has been developed for a long time and is very stable.

For Java, there are already some fairly stable libraries for all platforms: JIntelliType for Windows, which even supports media buttons, JXGrabKey for Linux systems, and ossuport-connector for Mac OSX. However, they all use jni, have a different interface, and it is not always convenient to work with libraries on jni, because you need to prescribe paths to native libraries, deal with system capacity, etc. Plus, this will be an interesting exercise in using JNA, because this task You can make it completely on java with a fairly small effort and get easily supported cross-platform code.

Windows


The easiest way to work with global hotkeys in Windows:
')
public class User32 {
static {
Native . register ( NativeLibrary. getInstance ( "user32" , W32APIOptions. DEFAULT_OPTIONS ) ) ;
}

public static final int MOD_ALT = 0x0001 ;
public static final int MOD_CONTROL = 0x0002 ;
public static final int MOD_SHIFT = 0x0004 ;
public static final int MOD_WIN = 0x0008 ;
public static final int WM_HOTKEY = 0x0312 ;

public static native boolean RegisterHotKey ( Pointer hWnd, int id, int fsModifiers, int vk ) ;
public static native boolean UnregisterHotKey ( Pointer hWnd, int id ) ;
public static native boolean PeekMessage ( MSG lpMsg, Pointer hWnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg ) ;

public static class MSG extends Structure {
public Pointer hWnd ;
public int message ;
public int wParam ;
public int lParam ;
public int time ;
public int x ;
public int y ;
}
}


Here we use the so-called direct mapping . Besides the fact that it is faster, it is easier to work with it, since you can make static import and use the methods as native ones. We need three methods from User32:

X11


Unfortunately, it will not be possible to use direct mapping for X11, as this for some reason causes errors when working with FreeBSD. Mapping looks a bit more complicated:

public interface X11 extends Library {
public static X11 Lib = ( X11 ) Native . loadLibrary ( "X11" , X11. class ) ;
public static final int GrabModeAsync = 1 ;
public static final int KeyPress = 2 ;

public static final int ShiftMask = ( 1 ) ;
public static final int LockMask = ( 1 << 1 ) ;
public static final int ControlMask = ( 1 << 2 ) ;
public static final int Mod1Mask = ( 1 << 3 ) ;
public static final int Mod2Mask = ( 1 << 4 ) ;
public static final int Mod3Mask = ( 1 << 5 ) ;
public static final int Mod4Mask = ( 1 << 6 ) ;
public static final int Mod5Mask = ( 1 << 7 ) ;

public Pointer XOpenDisplay ( String name ) ;
public NativeLong XDefaultRootWindow ( Pointer display ) ;
public byte XKeysymToKeycode ( Pointer display, long keysym ) ;
public int XGrabKey ( Pointer display, int code, int modifiers, NativeLong root, int ownerEvents, int pointerMode, int keyBoardMode ) ;
public int XUngrabKey ( Pointer display, int code, int modifiers, NativeLong root ) ;
public int XNextEvent ( Pointer display, XEvent event ) ;
public int XPending ( Pointer display ) ;
public int XCloseDisplay ( Pointer display ) ;

public static class XEvent extends Union {
public int type ;
public XKeyEvent xkey ;
public NativeLong [ ] pad = new NativeLong [ 24 ] ;
}

public static class XKeyEvent extends Structure {
public int type ; // of event
public NativeLong serial ; // # of last request processed by server
public int send_event ; // true if this came from a SendEvent request
public Pointer display ; // public display the event was read from
public NativeLong window ; // "event" window it is reported relative to
public NativeLong root ; // root window that the event occurred on
public NativeLong subwindow ; // child window
public NativeLong time ; // milliseconds
public int x, y ; // pointer x, y coordinates in event window
public int x_root, y_root ; // coordinates relative to root
public int state ; // key or button mask
public int keycode ; // detail
public int same_screen ; // same screen flag
}
}


Mac osx


The basis of the code for Mac OSX was made up by the workings of Torsten Uhlmann , the author of ossupport-connector:

public interface Carbon extends Library {
public static Carbon Lib = ( Carbon ) Native . loadLibrary ( "Carbon" , Carbon. class ) ;

public static final int cmdKey = 0x0100 ;
public static final int shiftKey = 0x0200 ;
public static final int optionKey = 0x0800 ;
public static final int controlKey = 0x1000 ;

// OS_TYPE concatenates string characters into int
private static final int kEventClassKeyboard = OS_TYPE ( "keyb" ) ;
private static final int typeEventHotKeyID = OS_TYPE ( "hkid" ) ;
private static final int kEventParamDirectObject = OS_TYPE ( "----" ) ;

public Pointer GetEventDispatcherTarget ( ) ;

public int InstallEventHandler ( Pointer inTarget, EventHandlerProcPtr inHandler, int inNumTypes, EventTypeSpec [ ] inList, Pointer inUserData, PointerByReference outRef ) ;
public int RegisterEventHotKey ( int inHotKeyCode, int inHotKeyModifiers, EventHotKeyID. ByValue inHotKeyID, Pointer inTarget, int inOptions, PointerByReference outRef ) ;
public int GetEventParameter ( Pointer inEvent, int inName, int inDesiredType, Pointer outActualType, int inBufferSize, IntBuffer outActualSize, EventHotKeyID outData ) ;
public int RemoveEventHandler ( Pointer inHandlerRef ) ;
public int UnregisterEventHotKey ( Pointer inHotKey ) ;

public class EventTypeSpec extends Structure {
public int eventClass ;
public int eventKind ;
}

public static class EventHotKeyID extends Structure {
public int signature ;
public int id ;

public static class ByValue extends EventHotKeyID implements Structure. ByValue {
}
}

public static interface EventHandlerProcPtr extends Callback {
public int callback ( Pointer inHandlerCallRef, Pointer inEvent, Pointer inUserData ) ;
}
}



Conclusion


In general, everything is quite simple, although there were some problems, such as crashes when using direct mapping on FreeBSD, JNA’s failure to get boolean in XGrabKey on an int, strange errors when passing a structure by reference, and not in Carbon, errors generated by X11 if the hotkey is already busy, which just cut down the program, the difficulty of finding any documentation on Carbon.

All this code is compiled into the jkeymaster library under the LGPL 3 license. The interface is based on KeyStroke from Swing; for Windows and X11, you can register media buttons — Play / Pause, Stop, Next Track, Previous Track.

Comments and patches are welcome.

ps The post is written by tulskiy , which, thanks to mgarin, is now in place. So all the benefits to him.

Source: https://habr.com/ru/post/124567/


All Articles