diff --git a/src/java/com/threerings/util/KeyboardManager.java b/src/java/com/threerings/util/KeyboardManager.java index 8a97e1782..fdc0529e5 100644 --- a/src/java/com/threerings/util/KeyboardManager.java +++ b/src/java/com/threerings/util/KeyboardManager.java @@ -1,12 +1,12 @@ // -// $Id: KeyboardManager.java,v 1.14 2002/11/08 08:17:35 ray Exp $ +// $Id: KeyboardManager.java,v 1.15 2003/01/12 00:26:39 shaper Exp $ package com.threerings.util; import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; import java.awt.Window; -import java.awt.event.KeyAdapter; + import java.awt.event.KeyEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; @@ -24,6 +24,8 @@ import com.samskivert.util.Interval; import com.samskivert.util.IntervalManager; import com.samskivert.util.ObserverList; +import com.threerings.util.keybd.Keyboard; + /** * The keyboard manager observes keyboard actions on a particular * component and posts commands associated with the key presses to the @@ -130,14 +132,23 @@ public class KeyboardManager } if (!enabled) { - // clear out any previous management we were engaged in + if (Keyboard.isAvailable()) { + // restore the original key auto-repeat settings + Keyboard.setKeyRepeat(_nativeRepeat); + } + + // clear out all of our key states releaseAllKeys(); _keys.clear(); + + // cease listening to all of our business if (_window != null) { _window.removeWindowFocusListener(this); _window = null; } _target.removeAncestorListener(this); + + // note that we no longer have the focus _focus = false; } else { @@ -157,6 +168,15 @@ public class KeyboardManager // assume the keyboard focus since we were just enabled _focus = true; + + if (Keyboard.isAvailable()) { + // note whether key auto-repeating was enabled + _nativeRepeat = Keyboard.isKeyRepeatEnabled(); + + // disable native key auto-repeating so that we can + // definitively ascertain key pressed/released events + Keyboard.setKeyRepeat(false); + } } // save off our new enabled state @@ -201,6 +221,12 @@ public class KeyboardManager */ protected void gainedFocus () { + if (Keyboard.isAvailable()) { + // disable key auto-repeating + Keyboard.setKeyRepeat(false); + } + + // note that we've regained the focus _focus = true; } @@ -210,7 +236,14 @@ public class KeyboardManager */ protected void lostFocus () { + if (Keyboard.isAvailable()) { + // restore key auto-repeating + Keyboard.setKeyRepeat(_nativeRepeat); + } + + // clear out all of our keyboard state releaseAllKeys(); + // note that we no longer have the focus _focus = false; } @@ -381,19 +414,19 @@ public class KeyboardManager _lastPress = time; _lastRelease = time; -// if (_iid == -1) { -// // register an interval to post the command associated -// // with the key press until the key is decidedly released -// _iid = IntervalManager.register(this, _pressDelay, null, true); -// if (DEBUG_EVENTS) { -// Log.info("Pressing key [key=" + _keyText + "]."); -// } + if (_iid == -1) { + // register an interval to post the command associated + // with the key press until the key is decidedly released + _iid = IntervalManager.register(this, _pressDelay, null, true); + if (DEBUG_EVENTS) { + Log.info("Pressing key [key=" + _keyText + "]."); + } if (_pressCommand != null) { // post the initial key press command postPress(time); } -// } + } } /** @@ -402,33 +435,33 @@ public class KeyboardManager public synchronized void setReleaseTime (long time) { release(time); -// _lastRelease = time; + _lastRelease = time; -// // handle key release events received so quickly after the key -// // press event that the press/release times are exactly equal -// // and, in intervalExpired(), we would therefore be unable to -// // distinguish between the key being initially pressed and the -// // actual true key release that's taken place. + // handle key release events received so quickly after the key + // press event that the press/release times are exactly equal + // and, in intervalExpired(), we would therefore be unable to + // distinguish between the key being initially pressed and the + // actual true key release that's taken place. -// // the only case I can think of that might result in this -// // happening is if the event manager class queues up a key -// // press and release event succession while other code is -// // executing, and when it comes time for it to dispatch the -// // events in its queue it manages to dispatch both of them to -// // us really-lickety-split. one would still think at least a -// // few milliseconds should pass between the press and release, -// // but in any case, we arguably ought to be watching for and -// // handling this case for posterity even though it would seem -// // unlikely or impossible, and so, now we do, which is a good -// // thing since it appears this does in fact happen, and not so -// // infrequently. -// if (_lastPress == _lastRelease) { -// if (DEBUG_EVENTS) { -// Log.warning("Insta-releasing key due to equal key " + -// "press/release times [key=" + _keyText + "]."); -// } -// release(time); -// } + // the only case I can think of that might result in this + // happening is if the event manager class queues up a key + // press and release event succession while other code is + // executing, and when it comes time for it to dispatch the + // events in its queue it manages to dispatch both of them to + // us really-lickety-split. one would still think at least a + // few milliseconds should pass between the press and release, + // but in any case, we arguably ought to be watching for and + // handling this case for posterity even though it would seem + // unlikely or impossible, and so, now we do, which is a good + // thing since it appears this does in fact happen, and not so + // infrequently. + if (_lastPress == _lastRelease) { + if (DEBUG_EVENTS) { + Log.warning("Insta-releasing key due to equal key " + + "press/release times [key=" + _keyText + "]."); + } + release(time); + } } /** @@ -438,9 +471,9 @@ public class KeyboardManager public synchronized void release (long timestamp) { // bail if we're not currently pressed -// if (_iid == -1) { -// return; -// } + if (_iid == -1) { + return; + } if (DEBUG_EVENTS) { Log.info("Releasing key [key=" + _keyText + "]."); @@ -484,21 +517,21 @@ public class KeyboardManager // we're certain the key is now up, or (c) repeat the key // command if we're certain the key is still down if (_lastRelease != _lastPress) { - if (deltaRelease < _repeatDelay) { - // register a one-shot sub-interval to - // definitively check whether the key was released - long delay = _repeatDelay - deltaRelease; - _siid = IntervalManager.register( - this, delay, new Long(_lastPress), false); - if (KeyboardManager.DEBUG_INTERVAL) { - Log.info("Registered sub-interval " + - "[id=" + _siid + "]."); - } +// if (deltaRelease < _repeatDelay) { +// // register a one-shot sub-interval to +// // definitively check whether the key was released +// long delay = _repeatDelay - deltaRelease; +// _siid = IntervalManager.register( +// this, delay, new Long(_lastPress), false); +// if (KeyboardManager.DEBUG_INTERVAL) { +// Log.info("Registered sub-interval " + +// "[id=" + _siid + "]."); +// } - } else { +// } else { // we know the key was released, so cease repeating release(now); - } +// } } else if (_pressCommand != null) { // post the key press command again @@ -663,4 +696,8 @@ public class KeyboardManager /** The operation used to notify observers of actual key events. */ protected KeyObserverOp _keyOp = new KeyObserverOp(); + + /** Whether native key auto-repeating was enabled when the keyboard + * manager was last enabled. */ + protected boolean _nativeRepeat; } diff --git a/src/java/com/threerings/util/keybd/Keyboard.java b/src/java/com/threerings/util/keybd/Keyboard.java new file mode 100644 index 000000000..a7e950776 --- /dev/null +++ b/src/java/com/threerings/util/keybd/Keyboard.java @@ -0,0 +1,56 @@ +// +// $Id: Keyboard.java,v 1.1 2003/01/12 00:26:39 shaper Exp $ + +package com.threerings.util.keybd; + +import com.threerings.util.Log; + +/** + * Provides access to the native operating system's auto-repeat keyboard + * settings. + */ +public class Keyboard +{ + /** + * Sets whether key auto-repeating is enabled. + */ + public static native void setKeyRepeat (boolean enabled); + + /** + * Returns whether key auto-repeating is enabled. + */ + public static native boolean isKeyRepeatEnabled (); + + /** + * Tests keyboard functionality. + */ + public static void main (String[] args) + { + boolean enabled = (args.length > 0 && args[0].equals("on")); + Keyboard.setKeyRepeat(enabled); + } + + /** + * Returns whether the native keyboard interface is available. + */ + public static boolean isAvailable () + { + return _haveLib; + } + + /** Whether the keyboard native library was successfully loaded. */ + protected static boolean _haveLib; + + static { + try { + System.loadLibrary("keybd"); + _haveLib = true; + Log.info("Loaded native keyboard library."); + + } catch (UnsatisfiedLinkError e) { + Log.warning("Failed to load native keyboard library " + + "[e=" + e + "]."); + _haveLib = false; + } + } +} diff --git a/src/java/com/threerings/util/keybd/com_threerings_util_keybd_Keyboard.h b/src/java/com/threerings/util/keybd/com_threerings_util_keybd_Keyboard.h new file mode 100644 index 000000000..04f524d0b --- /dev/null +++ b/src/java/com/threerings/util/keybd/com_threerings_util_keybd_Keyboard.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_threerings_util_keybd_Keyboard */ + +#ifndef _Included_com_threerings_util_keybd_Keyboard +#define _Included_com_threerings_util_keybd_Keyboard +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_threerings_util_keybd_Keyboard + * Method: setKeyRepeat + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_com_threerings_util_keybd_Keyboard_setKeyRepeat + (JNIEnv *, jclass, jboolean); + +/* + * Class: com_threerings_util_keybd_Keyboard + * Method: isKeyRepeatEnabled + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_com_threerings_util_keybd_Keyboard_isKeyRepeatEnabled + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/com/threerings/util/keybd/i686-Linux/Makefile b/src/java/com/threerings/util/keybd/i686-Linux/Makefile new file mode 100644 index 000000000..f1f6276a8 --- /dev/null +++ b/src/java/com/threerings/util/keybd/i686-Linux/Makefile @@ -0,0 +1,22 @@ +# +# $Id: Makefile,v 1.1 2003/01/12 00:26:39 shaper Exp $ + +CC=gcc +RM=rm + +INCLUDES=-I.. -I/usr/local/jdk1.4/include -I/usr/local/jdk1.4/include/linux +LIBRARIES_PATH=-L/usr/X11R6/lib +LIBRARIES=-lX11 +TARGET=libkeybd.so + +all: ${TARGET} + +${TARGET}: com_threerings_util_keybd_Keyboard.c + ${CC} ${INCLUDES} -c com_threerings_util_keybd_Keyboard.c \ + -o com_threerings_util_keybd_Keyboard.o + ${CC} -o ${TARGET} com_threerings_util_keybd_Keyboard.o \ + ${LIBRARIES} ${LIBRARIES_PATH} -shared + +clean: + -${RM} *.o + -${RM} ${TARGET} diff --git a/src/java/com/threerings/util/keybd/i686-Linux/com_threerings_util_keybd_Keyboard.c b/src/java/com/threerings/util/keybd/i686-Linux/com_threerings_util_keybd_Keyboard.c new file mode 100644 index 000000000..7cfc7ace7 --- /dev/null +++ b/src/java/com/threerings/util/keybd/i686-Linux/com_threerings_util_keybd_Keyboard.c @@ -0,0 +1,92 @@ +/* + * $Id: com_threerings_util_keybd_Keyboard.c,v 1.1 2003/01/12 00:26:39 shaper Exp $ + */ + +#include +#include +#include +#include "com_threerings_util_keybd_Keyboard.h" + +/* defines */ +#define MESSAGE_LENGTH (256) + +/* prototype definitions */ +Display* getXDisplay (JNIEnv* env); +void throwRuntimeException (JNIEnv* env, char* message); + +JNIEXPORT void JNICALL +Java_com_threerings_util_keybd_Keyboard_setKeyRepeat ( + JNIEnv* env, jclass class, jboolean enabled) +{ + Display* display = getXDisplay(env); + if (display == NULL) { + return; + } + + /* set the desired key auto-repeat state. */ + if (enabled) { + XAutoRepeatOn(display); + } else { + XAutoRepeatOff(display); + } + + /* close the display to save our changes */ + XCloseDisplay(display); +} + +JNIEXPORT jboolean JNICALL +Java_com_threerings_util_keybd_Keyboard_isKeyRepeatEnabled ( + JNIEnv* env, jclass class) +{ + XKeyboardState values; + Display* display = getXDisplay(env); + if (display == NULL) { + /* for now, assume auto-repeat is enabled */ + return JNI_TRUE; + } + + /* get the current keyboard control information */ + XGetKeyboardControl(display, &values); + + /* close the display */ + XCloseDisplay(display); + + return (values.global_auto_repeat) ? JNI_TRUE : JNI_FALSE; +} + +/* + * Returns a pointer to the X display, or null if an error occurred. + */ +Display* +getXDisplay (JNIEnv* env) +{ + char* disp = NULL; + Display* dpy = XOpenDisplay(disp); + if (dpy == NULL) { + char message[MESSAGE_LENGTH]; + snprintf(message, MESSAGE_LENGTH, + "Unable to open display [display=%s].\n", XDisplayName(disp)); + throwRuntimeException(env, message); + return NULL; + } + + /* printf("Opened display [disp=%s].\n", XDisplayName(disp)); */ + return dpy; +} + +/* + * Throws a runtime exception with the supplied message. + */ +void +throwRuntimeException (JNIEnv* env, char* message) +{ + jclass eclass = (*env)->FindClass(env, "java/lang/RuntimeException"); + if (eclass == 0) { + /* unable to find the exception class */ + fprintf(stderr, "Can't get runtime exception class?! [message=%s].\n", + message); + return; + } + + (*env)->ThrowNew(env, eclass, message); +} diff --git a/src/java/com/threerings/util/keybd/i686-Windows/Makefile b/src/java/com/threerings/util/keybd/i686-Windows/Makefile new file mode 100644 index 000000000..7fbee5934 --- /dev/null +++ b/src/java/com/threerings/util/keybd/i686-Windows/Makefile @@ -0,0 +1,27 @@ +# +# $Id: Makefile,v 1.1 2003/01/12 00:26:39 shaper Exp $ + +CROSSTOOLS_PATH=/usr/local/cross-tools +MINGW_PATH=${CROSSTOOLS_PATH}/i386-mingw32msvc +WIN32API_PATH=${CROSSTOOLS_PATH}/w32api + +CC=${MINGW_PATH}/bin/gcc +RM=rm + +INCLUDES=-I.. -I/usr/local/jdk1.4/include -I/usr/local/jdk1.4/include/linux \ + -I${CROSSTOOLS_PATH}/include -I${WIN32API_PATH}/include +LIBRARIES_PATH=-L${CROSSTOOLS_PATH}/lib -L${WIN32API_PATH}/lib +LIBRARIES= +TARGET=keybd.dll + +all: ${TARGET} + +${TARGET}: com_threerings_util_keybd_Keyboard.c + ${CC} ${INCLUDES} -c com_threerings_util_keybd_Keyboard.c \ + -o com_threerings_util_keybd_Keyboard.o + ${CC} -o ${TARGET} com_threerings_util_keybd_Keyboard.o \ + ${LIBRARIES} ${LIBRARIES_PATH} -shared + +clean: + -${RM} *.o + -${RM} ${TARGET} diff --git a/src/java/com/threerings/util/keybd/i686-Windows/com_threerings_util_keybd_Keyboard.c b/src/java/com/threerings/util/keybd/i686-Windows/com_threerings_util_keybd_Keyboard.c new file mode 100644 index 000000000..8a0e529db --- /dev/null +++ b/src/java/com/threerings/util/keybd/i686-Windows/com_threerings_util_keybd_Keyboard.c @@ -0,0 +1,104 @@ +/* + * $Id: com_threerings_util_keybd_Keyboard.c,v 1.1 2003/01/12 00:26:39 shaper Exp $ + */ + +#include +#include +#include +#include "com_threerings_util_keybd_Keyboard.h" + +/* prototype definitions */ +LRESULT CALLBACK KeyboardProc (int nCode, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK LowLevelKeyboardProc (int nCode, WPARAM wParam, LPARAM lParam); + +/* static global variables */ +static HHOOK gKeyHook; +static HINSTANCE gHInst; + +BOOL WINAPI +DllMain (HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved) +{ + /* save off our instance handle */ + fprintf(stderr, "In DllMain.\n"); + gHInst = (HINSTANCE)hinstDLL; +} + +JNIEXPORT void JNICALL +Java_com_threerings_util_keybd_Keyboard_setKeyRepeat ( + JNIEnv* env, jclass class, jboolean enabled) +{ + if (enabled) { + if (gKeyHook != NULL) { + fprintf(stderr, "Removing windows keyboard hook.\n"); + /* remove the keyboard hook */ + UnhookWindowsHookEx(gKeyHook); + gKeyHook = NULL; + } + + } else { + /* install the hook with which we usurp all keyboard events */ +/* gKeyHook = SetWindowsHookEx( */ +/* WH_KEYBOARD_LL, LowLevelKeyboardProc, hinstExe, 0); */ + + fprintf(stderr, "Setting windows keyboard hook.\n"); + gKeyHook = SetWindowsHookEx( + WH_KEYBOARD, KeyboardProc, gHInst, 0); + } +} + +JNIEXPORT jboolean JNICALL +Java_com_threerings_util_keybd_Keyboard_isKeyRepeatEnabled ( + JNIEnv* env, jclass class) +{ + /* since windows has no global key repeat enable/disable facility, we + * simply always return true here so that we'll be sure to "re-enable" + * key repeat, which will result in our properly removing the keyboard + * hook with which we trap key events. */ + fprintf(stderr, "isKeyRepeatEnabled stderr.\n"); + printf("isKeyRepeatEnabled stdout.\n"); + return JNI_TRUE; +} + +/* + * The keyboard event hook that eats all repeated keystrokes. + */ +LRESULT CALLBACK +KeyboardProc (int nCode, WPARAM wParam, LPARAM lParam) +{ + int repeatCount = (lParam & KF_REPEAT); + fprintf(stderr, "Key down [key=%d, repeatCount=%d].\n", + wParam, repeatCount); + return (repeatCount > 1) ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam); +} + +#if 0 + +/* + * The low-level keyboard event hook that eats all keystrokes. + */ +LRESULT CALLBACK +LowLevelKeyboardProc (int nCode, WPARAM wParam, LPARAM lParam) +{ + BOOL fEatKeystroke = FALSE; + + if (nCode == HC_ACTION) { + switch (wParam) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam; + fEatKeystroke = + ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0)) || + ((p->vkCode == VK_ESCAPE) && + ((p->flags & LLKHF_ALTDOWN) != 0)) || + ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & + 0x8000) != 0)); + break; + } + } + + return (fEatKeystroke) ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam); +} + +#endif