JNAとは何ですか?
Javaは、オペレーティングシステムとやり取りするためのブリッジを作成する必要があるため、デスクトップと友達になることは困難です。 これらの機能の1つはグローバルなホットキーです。これは、オーディオプレーヤーで非常に人気があり、非表示の状態でも特定のキーボードショートカットまたはメディアボタンを使用してプログラムを制御できます。 JNAが助けになります-ネイティブライブラリを呼び出すためのjniとlibffiのアドオンで、ほぼすべての一般的なプラットフォームをサポートし、長い間開発されており、非常に安定しています。
すべてのプラットフォーム用のJava用のかなり安定したライブラリが既にいくつかあります。Windows用のJIntelliType(メディアボタンもサポート)、Linuxシステム用のJXGrabKey 、Mac OSX用のossuport-connectorです。 ただし、これらはすべてjniを使用し、異なるインターフェイスを持ち、ネイティブライブラリへのパスを登録したり、システム容量を処理したりする必要があるため、jniでライブラリを操作するのが常に便利であるとは限りません。かなり少ない労力でJavaで完全に実行でき、クロスプラットフォームコードを簡単にサポートできます。
窓
Windowsでグローバルホットキーを使用する最も簡単な方法は次のとおりです。
パブリック クラス User32 {
static {
ネイティブ 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 (ポインター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 ) ;
パブリック スタティック クラス MSG は Structure {
パブリックポインターhWnd ;
public intメッセージ;
public int wParam ;
public int lParam ;
パブリック int時間。
public int x ;
public int y ;
}
}
ここでは、いわゆる直接マッピングを使用します。 静的インポートを行い、メソッドをネイティブメソッドとして使用できるため、高速であることに加えて、作業が簡単になります。 User32の3つのメソッドが必要です。
- RegisterHotKey-実際にホットキーを登録します。 ウィンドウがないため、最初のパラメーターはnullに設定できます。 2番目のパラメーター-ホットキーの一意の番号は、私たちによって生成され、その後、ホットキーを識別してUnregisterHotKeyを使用して削除するために使用されます。 fsModifiersには、必要な修飾子(ctrl、shift、alt、またはwin)を記述します。 vkでは、仮想コードを記述します。 興味深いことに、AWTのKeyEventで使用される仮想コードは、ほとんどの場合Windowsの仮想コードと一致します。
// WIN + Fの組み合わせを登録します
RegisterHotKey ( null 、1、0x8、 KeyEvent。VK_F ) ;
- PeekMessage-着信メッセージをチェックし、MSG構造に詰まらせます。 ここでは、すべてのメソッドを同じスレッドから呼び出す必要があり、このスレッドを制御できるようにする必要があるため、ブロッキングGetMessageの代わりに非ブロッキングPeekMessage呼び出しが使用されます。
MSG msg = 新しい MSG ( ) ;
while ( listen ) {
while ( PeekMessage ( msg、 null 、 0、0 、PM_REMOVE ) ) {
if ( msg。message == WM_HOTKEY ) {
システム アウト 。 println ( "Yattaaaa。id with hotkey" + msg。wParam ) ;
}
}
スレッド 睡眠 ( 300 ) ;
}
ここでは、WIN + Fの組み合わせを登録し、300ミリ秒ごとにメッセージを確認します。
X11
残念なことに、FreeBSDで作業しているときに何らかの理由でエラーが発生するため、X11に直接マッピングを使用することはできません。 マッピングはもう少し複雑に見えます:
パブリック インターフェイス X11 はライブラリを拡張します{
public static X11 Lib = ( X11 ) ネイティブ 。 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 ) ;
パブリックポインターXOpenDisplay ( 文字列名) ;
パブリック NativeLong XDefaultRootWindow (ポインター表示) ;
public byte XKeysymToKeycode (ポインター表示、 長いキーシム) ;
public int XGrabKey (ポインター表示、 intコード、 int修飾子、NativeLongルート、 int ownerEvents、 int pointerMode、 int keyBoardMode ) ;
public int XUngrabKey (ポインター表示、 intコード、 int修飾子、NativeLongルート) ;
public int XNextEvent (ポインター表示、XEventイベント) ;
パブリック int XPending (ポインター表示) ;
public int XCloseDisplay (ポインター表示) ;
public static class XEvent は Union {を 拡張し ます
パブリック int型。
public XKeyEvent xkey ;
public NativeLong [ ] pad = new NativeLong [ 24 ] ;
}
public static class XKeyEvent extends Structure {
パブリック int型。 //イベントの
ネイティブNativeLongシリアル; //サーバーによって処理された最後のリクエストの数
public int send_event ; //これがSendEventリクエストから来た場合はtrue
パブリックポインターディスプレイ; // publicイベントの読み取り元を表示します
パブリック NativeLongウィンドウ。 //相対で報告される「イベント」ウィンドウ
パブリック NativeLongルート。 //イベントが発生したルートウィンドウ
パブリック NativeLongサブウィンドウ。 //子ウィンドウ
パブリック NativeLong時間; //ミリ秒
public int x、y ; //イベントウィンドウ内のポインターx、y座標
public int x_root、y_root ; //ルートを基準にした座標
パブリック int状態。 //キーまたはボタンマスク
public int keycode ; //詳細
public int same_screen ; //同じ画面フラグ
}
}
- XOpenDisplay、XCloseDisplay、XDefaultRootWindow-デフォルトのディスプレイとルートウィンドウを取得して閉じます。
- XKeysymToKeycode-文字(文字の説明はkeysymdef.hで取得されます )をキーボードコードに変換します。後で必要になります。
//文字と数字の場合、文字番号はKeyEventの値とも一致します
バイトコード= XKeysymToKeycode ( display、 KeyEvent。VK_F ) ;
- XGrabKey、XUngrabKey-ホットキーを登録および削除します。 コードでは、XKeysymToKeycodeで以前に取得した値をここに記述し、修飾子は修飾子に記述し、ルートにはXDefaultRootWindowからの値を記述します。 残りの値は単位で詰まっています。 興味深いことに、X11では、押されたScrollLock、NumLock、CapsLockおよびその他のロックも修飾子と見なされるため、可能な組み合わせごとに1つのホットキーの代わりに16を登録する必要があります。
//このハックはdeadbeefプレーヤーのグローバルホットキープラグインにあります
for ( int flags = 0 ; flags < 16 ; i ++ ) {
int ret = modifiers ;
if ( ( flags & 1 ) != 0 )
ret | = LockMask ;
if ( ( flags & 2 ) != 0 )
ret | = Mod2Mask ;
if ( ( flags & 4 ) != 0 )
ret | = Mod3Mask ;
if ( ( flags & 8 ) != 0 )
ret | = Mod5Mask ;
XGrabKey ( display、code、ret、root、 1 、GrabModeAsync、GrabModeAsync ) ;
// XUngrabKey(display、code、ret、root);
}
また、プログラムの終了時にホットキーを削除する必要がないWindowsとは異なり、XUngrabKeyを呼び出さないと、Xは再起動するまでホットキーを保持します。
- XPendingおよびXNextEvent-存在を確認し、次のイベントを取得します。
while ( listen ) {
while ( Lib。XPending ( display ) > 0 ) {
Lib。 XNextEvent ( display、event ) ;
if ( event。type == KeyPress ) {
//ユニオンXEventからイベントを読み取ります
// JNAはどのフィールドをユニオンに入力するかを知らないため、
//したがって、どのフィールドをカウントするかを指示する必要があります。
XKeyEvent xkey = ( XKeyEvent )イベント。 readField ( "xkey" ) ;
//ガベージ修飾子をクリアします
int state = xkey。 状態 & ( ShiftMask | ControlMask | Mod1Mask | Mod4Mask ) ;
システム アウト 。 println ( "Yattaaaa、hotkey with code:" + xkey。keycode + "and modifiers:" + state ) ;
}
}
スレッド 睡眠 ( 300 ) ;
}
Mac OSX
Mac OSXのコードの基礎は、ossupport-connectorの作者であるTorsten Uhlmannの仕事です。
パブリック インターフェイス Carbon がライブラリを拡張 {
public static Carbon Lib = (カーボン) ネイティブ 。 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は文字列文字を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 ( Point inHotKey ) ;
パブリック クラス EventTypeSpec は Structure {
public int eventClass ;
public int eventKind ;
}
public static class EventHotKeyID extends Structure {
パブリック int署名。
public int id ;
パブリック スタティック クラス ByValue は、 EventHotKeyIDを拡張し、 Structureを実装します。 ByValue {
}
}
public static interface EventHandlerProcPtr extends Callback {
public intコールバック( Pointer inHandlerCallRef、Pointer inEvent、Pointer inUserData ) ;
}
}
- GetEventDispatcherTarget-システムイベントハンドラーへのリンクを取得する
- InstallEventHandler-ハンドラーをこのイベントハンドラーに追加します。 他のプラットフォームとは異なり、Carbonのイベントはコールバックを介して非同期的に送信されます。
eventHandlerReference = new PointerByReference ( ) ;
//実際にはハンドラー自体
keyListener = new EventHandler ( ) ;
// 1つの構造から配列を作成するJNAマジック
EventTypeSpec [ ] eventTypes = ( EventTypeSpec [ ] ) ( 新しい EventTypeSpec ( ) 。 ToArray ( 1 ) ) ;
eventTypes [ 0 ] 。 eventClass = kEventClassKeyboard ;
eventTypes [ 0 ] 。 eventKind = kEventHotKeyPressed ;
int status = Lib。 InstallEventHandler ( Lib。GetEventDispatcherTarget ( ) 、keyListener、 1 、eventTypes、 null 、eventHandlerReference ) ;
ここでは、キーボードメッセージ、つまりホットキーを押すことを処理することを示します。
EventHandlerReferenceには、RemoveEventHandlerで必要なハンドラー参照が付属しています。
イベントハンドラは次のとおりです。
プライベート クラス EventHandler は Carbonを実装します。 EventHandlerProcPtr {
public intコールバック( Pointer inHandlerCallRef、Pointer inEvent、Pointer inUserData ) {
EventHotKeyID eventHotKeyID = new EventHotKeyID ( ) ;
//イベントパラメータを取得
int ret = Lib。 GetEventParameter ( inEvent、kEventParamDirectObject、typeEventHotKeyID、 null 、eventHotKeyID。Size ( ) 、 null 、eventHotKeyID ) ;
if ( ret != 0 ) {
ロガー。 警告 ( 「イベントパラメータを取得できませんでした。エラーコード:」 + ret ) ;
} else {
//ここでイベントIDを取得して処理します
int eventId = eventHotKeyID。 id ;
ロガー。 info ( "受信したイベントID:" + eventId ) ;
}
0を 返し ます 。
}
}
- RegisterEventHotKey-ホットキーを登録します。 入力はキーコードです。 コード表はこちらです。 以下は修飾子のリストです。 次に、EventHotKeyID.ByValue構造体に識別子と4文字の署名がハンマーで挿入されます。 ByValueが使用されるのは、デフォルトでは、構造が参照によって渡され、値によって必要になるためです。 UnregisterEventHotKeyで使用されるこのホットキーへのリンクが返されます。
EventHotKeyID。 ByValue hotKeyReference = 新しい EventHotKeyID。 ByValue ( ) ;
hotKeyReference。 id = 1 ;
hotKeyReference。 署名 = OS_TYPE ( "hk01" ) ;
PointerByReference gMyHotKeyRef = new PointerByReference ( ) ;
int status = Lib。 RegisterEventHotKey (コード、修飾子、hotKeyReference、 Lib。GetEventDispatcherTarget ( ) 、 0 、gMyHotKeyRef ) ;
おわりに
一般に、すべてが非常に単純ですが、FreeBSDで直接マッピングを使用するとクラッシュするなどの問題がありましたが、JNAはXGrabKeyのブール値をintにマッピングすることを拒否しました。ホットキーがすでに使用されている場合、プログラムが単純に削減されるため、Carbonに関するドキュメントを見つけるのが困難になります。
このコードはすべて、LGPL 3ライセンスの下でjkeymasterライブラリにコンパイルされ、インターフェイスはSwingのKeyStrokeに基づいており、WindowsおよびX11の場合、メディアボタン(再生/一時停止、停止、次のトラック、前のトラック)を登録できます。
コメントとパッチを歓迎します。
ps投稿はtulskiyによって書かれました 。これはmgarinのおかげで今ハブにあります 彼へのすべてのプラス。