Behold, Nenya, Ring of Water and repository for our media and animation related

goodies, both Java 2D and LWJGL/JME 3D.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2006-06-23 18:07:28 +00:00
commit c2117ee86d
570 changed files with 61913 additions and 0 deletions
@@ -0,0 +1,141 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.net.URL;
import com.samskivert.util.ResultListener;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
/**
* Encapsulates a bunch of hackery needed to invoke an external web browser
* from within a Java application.
*/
public class BrowserUtil
{
/**
* Opens the user's web browser with the specified URL.
*
* @param url the URL to display in an external browser.
* @param listener a listener to be notified if we failed to launch the
* browser. <em>Note:</em> it will not be notified of success.
*/
public static void browseURL (URL url, ResultListener listener)
{
browseURL(url, listener, "firefox");
}
/**
* Opens the user's web browser with the specified URL.
*
* @param url the URL to display in an external browser.
* @param listener a listener to be notified if we failed to launch the
* browser. <em>Note:</em> it will not be notified of success.
* @param genagent the path to the browser to execute on non-Windows,
* non-MacOS.
*/
public static void browseURL (
URL url, ResultListener listener, String genagent)
{
String[] cmd;
if (RunAnywhere.isWindows()) {
// TODO: test this on Vinders 98
// cmd = new String[] { "rundll32", "url.dll,FileProtocolHandler",
// url.toString() };
String osName = System.getProperty("os.name");
if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
cmd = new String[] { "command.com", "/c", "start",
"\"" + url.toString() + "\"" };
} else {
cmd = new String[] { "cmd.exe", "/c", "start", "\"\"",
"\"" + url.toString() + "\"" };
}
} else if (RunAnywhere.isMacOS()) {
cmd = new String[] { "open", url.toString() };
} else { // Linux, Solaris, etc
cmd = new String[] { genagent, url.toString() };
}
Log.info("Browsing URL [cmd=" + StringUtil.join(cmd, " ") + "].");
try {
Process process = Runtime.getRuntime().exec(cmd);
BrowserTracker tracker = new BrowserTracker(process, url, listener);
tracker.start();
} catch (Exception e) {
Log.warning("Failed to launch browser [url=" + url +
", error=" + e + "].");
listener.requestFailed(e);
}
}
protected static class BrowserTracker extends Thread
{
public BrowserTracker (Process process, URL url, ResultListener rl) {
super("BrowserLaunchWaiter");
setDaemon(true);
_process = process;
_url = url;
_listener = rl;
}
public void run () {
try {
_process.waitFor();
int rv = _process.exitValue();
if (rv == 0) {
return;
}
String errmsg = "Launched browser failed [rv=" + rv + "].";
Log.warning(errmsg);
if (!RunAnywhere.isWindows()) {
_listener.requestFailed(new Exception(errmsg));
return;
}
// if we're on windows, make a last ditch effort
String[] cmd = new String[] {
"C:\\Program Files\\Internet Explorer\\" +
"IEXPLORE.EXE", "\"" + _url.toString() + "\""};
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
rv = process.exitValue();
if (rv != 0) {
errmsg = "Failed to launch iexplore.exe [rv=" + rv + "].";
Log.warning(errmsg);
_listener.requestFailed(new Exception(errmsg));
}
} catch (Exception e) {
Log.logStackTrace(e);
_listener.requestFailed(e);
}
}
protected Process _process;
protected URL _url;
protected ResultListener _listener;
};
}
@@ -0,0 +1,104 @@
//
// $Id: DirectionCodes.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
/**
* A single, top-level location for the definition of compass direction
* constants, which are used by a variety of Narya services.
*/
public interface DirectionCodes
{
/** A direction code indicating no direction. */
public static final int NONE = -1;
/** A direction code indicating moving left. */
public static final int LEFT = 0;
/** A direction code indicating moving right. */
public static final int RIGHT = 1;
/** A direction code indicating a counter-clockwise rotation. */
public static final int CCW = 0;
/** A direction code indicating a clockwise rotation. */
public static final int CW = 1;
/** A direction code indicating horizontal movement. */
public static final int HORIZONTAL = 0;
/** A direction code indicating vertical movement. */
public static final int VERTICAL = 1;
/** A direction code indicating southwest. */
public static final int SOUTHWEST = 0;
/** A direction code indicating west. */
public static final int WEST = 1;
/** A direction code indicating northwest. */
public static final int NORTHWEST = 2;
/** A direction code indicating north. */
public static final int NORTH = 3;
/** A direction code indicating northeast. */
public static final int NORTHEAST = 4;
/** A direction code indicating east. */
public static final int EAST = 5;
/** A direction code indicating southeast. */
public static final int SOUTHEAST = 6;
/** A direction code indicating south. */
public static final int SOUTH = 7;
/** The number of basic compass directions. */
public static final int DIRECTION_COUNT = 8;
/** A direction code indicating west by southwest. */
public static final int WESTSOUTHWEST = 8;
/** A direction code indicating west by northwest. */
public static final int WESTNORTHWEST = 9;
/** A direction code indicating north by northwest. */
public static final int NORTHNORTHWEST = 10;
/** A direction code indicating north by northeast. */
public static final int NORTHNORTHEAST = 11;
/** A direction code indicating east by northeast. */
public static final int EASTNORTHEAST = 12;
/** A direction code indicating east by southeast. */
public static final int EASTSOUTHEAST = 13;
/** A direction code indicating south by southeast. */
public static final int SOUTHSOUTHEAST = 14;
/** A direction code indicating south by southwest. */
public static final int SOUTHSOUTHWEST = 15;
/** The number of fine compass directions. */
public static final int FINE_DIRECTION_COUNT = 16;
}
@@ -0,0 +1,338 @@
//
// $Id: DirectionUtil.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.awt.Point;
import com.samskivert.util.IntListUtil;
/**
* Direction related utility functions.
*/
public class DirectionUtil implements DirectionCodes
{
/**
* Returns an array of names corresponding to each direction constant.
*/
public static String[] getDirectionNames ()
{
return DIR_STRINGS;
}
/**
* Returns a string representation of the supplied direction code.
*/
public static String toString (int direction)
{
return ((direction >= 0) && (direction < FINE_DIRECTION_COUNT)) ?
DIR_STRINGS[direction] : "INVALID";
}
/**
* Returns an abbreviated string representation of the supplied
* direction code.
*/
public static String toShortString (int direction)
{
return ((direction >= 0) && (direction < FINE_DIRECTION_COUNT)) ?
SHORT_DIR_STRINGS[direction] : "?";
}
/**
* Returns the direction code that corresponds to the supplied string
* or {@link #NONE} if the string does not correspond to a known
* direction code.
*/
public static int fromString (String dirstr)
{
for (int ii = 0; ii < FINE_DIRECTION_COUNT; ii++) {
if (DIR_STRINGS[ii].equals(dirstr)) {
return ii;
}
}
return NONE;
}
/**
* Returns the direction code that corresponds to the supplied short
* string or {@link #NONE} if the string does not correspond to a
* known direction code.
*/
public static int fromShortString (String dirstr)
{
for (int ii = 0; ii < FINE_DIRECTION_COUNT; ii++) {
if (SHORT_DIR_STRINGS[ii].equals(dirstr)) {
return ii;
}
}
return NONE;
}
/**
* Returns a string representation of an array of direction codes. The
* directions are represented by the abbreviated names.
*/
public static String toString (int[] directions)
{
StringBuilder buf = new StringBuilder("{");
for (int i = 0; i < directions.length; i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(toShortString(directions[i]));
}
return buf.append("}").toString();
}
/**
* Rotates the requested <em>fine</em> direction constant clockwise by
* the requested number of ticks.
*/
public static int rotateCW (int direction, int ticks)
{
for (int ii = 0; ii < ticks; ii++) {
direction = FINE_CW_ROTATE[direction];
}
return direction;
}
/**
* Rotates the requested <em>fine</em> direction constant
* counter-clockwise by the requested number of ticks.
*/
public static int rotateCCW (int direction, int ticks)
{
for (int ii = 0; ii < ticks; ii++) {
direction = FINE_CCW_ROTATE[direction];
}
return direction;
}
/**
* Returns the opposite of the specified direction.
*/
public static int getOpposite (int direction)
{
return rotateCW(direction, FINE_CW_ROTATE.length/2);
}
/**
* Get the direction closest to the specified direction, out of
* the directions in the possible list (preferring a clockwise match).
*/
public static int getClosest (int direction, int[] possible)
{
return getClosest(direction, possible, true);
}
/**
* Get the direction closest to the specified direction, out of
* the directions in the possible list.
*
* @param preferCW whether to prefer a clockwise match or a
* counter-clockwise match.
*/
public static int getClosest (int direction, int[] possible,
boolean preferCW)
{
// rotate a tick at a time, looking for matches
int first = direction;
int second = direction;
for (int ii = 0; ii <= FINE_DIRECTION_COUNT / 2; ii++) {
if (IntListUtil.contains(possible, first)) {
return first;
}
if (ii != 0 && IntListUtil.contains(possible, second)) {
return second;
}
first = preferCW ? rotateCW(first, 1) : rotateCCW(first, 1);
second = preferCW ? rotateCCW(second, 1) : rotateCW(second, 1);
}
return NONE;
}
/**
* Returns which of the eight compass directions that point
* <code>b</code> lies in from point <code>a</code> as one of the
* {@link DirectionCodes} direction constants. <em>Note:</em> that the
* coordinates supplied are assumed to be logical (screen) rather than
* cartesian coordinates and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getDirection (Point a, Point b)
{
return getDirection(a.getX(), a.getY(), b.getX(), b.getY());
}
/**
* Returns which of the eight compass directions that point
* <code>b</code> lies in from point <code>a</code> as one of the
* {@link DirectionCodes} direction constants. <em>Note:</em> that the
* coordinates supplied are assumed to be logical (screen) rather than
* cartesian coordinates and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getDirection (int ax, int ay, int bx, int by)
{
return getDirection(Math.atan2(by-ay, bx-ax));
}
/**
* Returns which of the eight compass directions that point
* <code>b</code> lies in from point <code>a</code> as one of the
* {@link DirectionCodes} direction constants. <em>Note:</em> that the
* coordinates supplied are assumed to be logical (screen) rather than
* cartesian coordinates and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getDirection (double ax, double ay, double bx, double by)
{
return getDirection(Math.atan2(by-ay, bx-ax));
}
/**
* Returns which of the eight compass directions is associated with
* the specified angle theta. <em>Note:</em> that the angle supplied
* is assumed to increase clockwise around the origin (which screen
* angles do) rather than counter-clockwise around the origin (which
* cartesian angles do) and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getDirection (double theta)
{
theta = ((theta + Math.PI) * 4) / Math.PI;
return (int)(Math.round(theta) + WEST) % 8;
}
/**
* Returns which of the sixteen compass directions that point
* <code>b</code> lies in from point <code>a</code> as one of the
* {@link DirectionCodes} direction constants. <em>Note:</em> that the
* coordinates supplied are assumed to be logical (screen) rather than
* cartesian coordinates and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getFineDirection (Point a, Point b)
{
return getFineDirection(a.x, a.y, b.x, b.y);
}
/**
* Returns which of the sixteen compass directions that point
* <code>b</code> lies in from point <code>a</code> as one of the
* {@link DirectionCodes} direction constants. <em>Note:</em> that the
* coordinates supplied are assumed to be logical (screen) rather than
* cartesian coordinates and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getFineDirection (int ax, int ay, int bx, int by)
{
return getFineDirection(Math.atan2(by-ay, bx-ax));
}
/**
* Returns which of the sixteen compass directions is associated with
* the specified angle theta. <em>Note:</em> that the angle supplied
* is assumed to increase clockwise around the origin (which screen
* angles do) rather than counter-clockwise around the origin (which
* cartesian angles do) and <code>NORTH</code> is considered to point
* toward the top of the screen.
*/
public static int getFineDirection (double theta)
{
theta = ((theta + Math.PI) * 8) / Math.PI;
return ANGLE_MAP[(int)Math.round(theta) % FINE_DIRECTION_COUNT];
}
/**
* Move the specified point in the specified screen direction,
* adjusting by the specified adjustments. Fine directions are
* not supported.
*/
public static void moveDirection (Point p, int direction, int dx, int dy)
{
if (direction >= DIRECTION_COUNT) {
throw new IllegalArgumentException(
"Fine coordinates not supported.");
}
switch (direction) {
case NORTH: case NORTHWEST: case NORTHEAST: p.y -= dy;
}
switch (direction) {
case SOUTH: case SOUTHWEST: case SOUTHEAST: p.y += dy;
}
switch (direction) {
case WEST: case SOUTHWEST: case NORTHWEST: p.x -= dx;
}
switch (direction) {
case EAST: case SOUTHEAST: case NORTHEAST: p.x += dx;
}
}
/** Direction constant string names. */
protected static final String[] DIR_STRINGS = {
"SOUTHWEST", "WEST", "NORTHWEST", "NORTH",
"NORTHEAST", "EAST", "SOUTHEAST", "SOUTH",
"WESTSOUTHWEST", "WESTNORTHWEST", "NORTHNORTHWEST", "NORTHNORTHEAST",
"EASTNORTHEAST", "EASTSOUTHEAST", "SOUTHSOUTHEAST", "SOUTHSOUTHWEST",
};
/** Abbreviated direction constant string names. */
protected static final String[] SHORT_DIR_STRINGS = {
"SW", "W", "NW", "N", "NE", "E", "SE", "S",
"WSW", "WNW", "NNW", "NNE", "ENE", "ESE", "SSE", "SSW",
};
/** Used to rotate a fine compass direction clockwise. */
protected static final int[] FINE_CW_ROTATE = {
/* SW -> */ WESTSOUTHWEST, /* W -> */ WESTNORTHWEST,
/* NW -> */ NORTHNORTHWEST, /* N -> */ NORTHNORTHEAST,
/* NE -> */ EASTNORTHEAST, /* E -> */ EASTSOUTHEAST,
/* SE -> */ SOUTHSOUTHEAST, /* S -> */ SOUTHSOUTHWEST,
/* WSW -> */ WEST, /* WNW -> */ NORTHWEST,
/* NNW -> */ NORTH, /* NNE -> */ NORTHEAST,
/* ENE -> */ EAST, /* ESE -> */ SOUTHEAST,
/* SSE -> */ SOUTH, /* SSW -> */ SOUTHWEST
};
/** Used to rotate a fine compass direction counter-clockwise. */
protected static final int[] FINE_CCW_ROTATE = {
/* SW -> */ SOUTHSOUTHWEST, /* W -> */ WESTSOUTHWEST,
/* NW -> */ WESTNORTHWEST, /* N -> */ NORTHNORTHWEST,
/* NE -> */ NORTHNORTHEAST, /* E -> */ EASTNORTHEAST,
/* SE -> */ EASTSOUTHEAST, /* S -> */ SOUTHSOUTHEAST,
/* WSW -> */ SOUTHWEST, /* WNW -> */ WEST,
/* NNW -> */ NORTHWEST, /* NNE -> */ NORTH,
/* ENE -> */ NORTHEAST, /* ESE -> */ EAST,
/* SSE -> */ SOUTHEAST, /* SSW -> */ SOUTH
};
/** Used to map an angle to a fine compass direction. */
protected static final int[] ANGLE_MAP = {
WEST, WESTNORTHWEST, NORTHWEST, NORTHNORTHWEST, NORTH, NORTHNORTHEAST,
NORTHEAST, EASTNORTHEAST, EAST, EASTSOUTHEAST, SOUTHEAST,
SOUTHSOUTHEAST, SOUTH, SOUTHSOUTHWEST, SOUTHWEST, WESTSOUTHWEST };
}
@@ -0,0 +1,177 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import com.samskivert.util.Interval;
import com.samskivert.util.RunQueue;
/**
* Used to track user idleness in an AWT application.
*/
public abstract class IdleTracker
{
/**
* Creates an idle tracker that will report idleness (via {@link
* #idledOut}) after <code>toIdleTime</code> milliseconds have elapsed.
* After an additional <code>toAbandonTime</code> milliseconds have
* elapsed, we will report that the user has {@link #abandonedShip}.
*/
public IdleTracker (long toIdleTime, long toAbandonTime)
{
_toIdleTime = toIdleTime;
_toAbandonTime = toAbandonTime;
// initialize our last event time
_lastEvent = getTimeStamp();
}
public void start (KeyboardManager keymgr, RunQueue rqueue)
{
// we want to observe all mouse and keyboard events
long eventMask =
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.MOUSE_WHEEL_EVENT_MASK |
AWTEvent.KEY_EVENT_MASK;
// add the global event listener
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
public void eventDispatched (AWTEvent event) {
handleUserActivity();
}
}, eventMask);
// and tie into the keyboard manager if one is provided
if (keymgr != null) {
keymgr.registerKeyObserver(new KeyboardManager.KeyObserver() {
public void handleKeyEvent (
int id, int keyCode, long timestamp) {
handleUserActivity();
}
});
}
// register an interval to periodically check our last activity time
new Interval(rqueue) {
public void expired () {
checkIdle();
}
}.schedule(_toIdleTime/3, true);
}
/**
* Called when the client has been idle for {@link #_toIdleTime}
* milliseconds.
*/
protected abstract void idledOut ();
/**
* Called when the client becomes non-idle after we have previously
* reported their idleness.
*/
protected abstract void idledIn ();
/**
* Called when the client has been idle for {@link #_toIdleTime} plus
* {@link #_toAbandonTime} milliseconds.
*/
protected abstract void abandonedShip ();
/**
* This should return a timestamp. We would use {@link
* System#currentTimeMillis} except that on Windows that sometimes does
* strange things like leap forward in time causing immediate idleness.
*/
protected abstract long getTimeStamp ();
/**
* Called with any keyboard or mouse events performed on the frame so
* as to note user activity as it pertains to tracking the client idle
* state.
*/
protected void handleUserActivity ()
{
// note the time of the last user action
_lastEvent = getTimeStamp();
// idle-in if appropriate
if (_state != ACTIVE) {
_state = ACTIVE;
idledIn();
}
}
/**
* Checks the last user event time and posts a command to idle them
* out if they've been inactive for too long, or log them out if
* they've been idle for too long.
*/
protected void checkIdle ()
{
long now = getTimeStamp();
switch (_state) {
case ACTIVE:
// check whether they've idled out
if (now >= (_lastEvent + _toIdleTime)) {
Log.info("User idle for " + (now-_lastEvent) + "ms.");
_state = IDLE;
idledOut();
}
break;
case IDLE:
// check whether they've been idle for too long
if (now >= (_lastEvent + _toIdleTime + _toAbandonTime)) {
Log.info("User idle for " + (now-_lastEvent) + "ms. " +
"Abandoning ship.");
_state = ABANDONED;
abandonedShip();
}
break;
}
}
// /** The user's current state. */
// protected static enum State { ACTIVE, IDLE, ABANDONED };
/** The duration after which we declare the user to be idle. */
protected long _toIdleTime;
/** The duration after which we declare the user to have abandoned ship. */
protected long _toAbandonTime;
/** The time of the last mouse or keyboard event; used to track
* whether the user is idle. */
protected long _lastEvent;
/** Whether the user is currently active, idle or abandoned. */
protected int _state = ACTIVE;
protected static final int ACTIVE = 0;
protected static final int IDLE = 1;
protected static final int ABANDONED = 2;
}
@@ -0,0 +1,275 @@
//
// $Id: KeyDispatcher.java 4181 2006-06-07 21:54:12Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.awt.Component;
import java.awt.Window;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.text.JTextComponent;
import com.samskivert.util.HashIntMap;
/**
* Handles dispatching special global key pressed and released events to
* those that care to monitor and process such things.
*
* <p> A {@link JTextComponent} may registered as a "chat grabber" via
* {@link #pushChatGrabber} so as to capture typed chat characters
* regardless of which component currently has the focus.
*
* <p> Components may also register as global {@link KeyListener}s via
* {@link #addGlobalKeyListener} to always be notified of key press and
* release events for all keys.
*/
public class KeyDispatcher
implements KeyEventDispatcher, AncestorListener, WindowFocusListener
{
/**
* Constructs a key dispatcher.
*/
public KeyDispatcher (Window window)
{
// save things off
_window = window;
// listen to window events on our main window so that we can release
// keys when the mouse leaves the window
_window.addWindowFocusListener(this);
// monitor key events from the central dispatch mechanism
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addKeyEventDispatcher(this);
}
/**
* Shuts down the key dispatcher.
*/
public void shutdown ()
{
// cease monitoring key events
KeyboardFocusManager.getCurrentKeyboardFocusManager().
removeKeyEventDispatcher(this);
// cease observing our window
_window.removeWindowFocusListener(this);
}
/**
* Makes the specified component the new grabber of key typed events
* that look like they're chat-related per {@link #isChatCharacter}.
*/
public void pushChatGrabber (JTextComponent comp)
{
// note this component as the new chat grabber
_curChatGrabber = comp;
// add the component to the list of grabbers
_chatGrabbers.addLast(comp);
comp.addAncestorListener(this);
// and request to give the new component the focus since that's a
// sensible thing to do as it's aiming to nab all key events
// henceforth
comp.requestFocusInWindow();
}
/**
* Removes the specified component from the list of chat grabbers so
* that it will no longer be notified of chat-like key events.
*/
public void removeChatGrabber (JTextComponent comp)
{
// remove the component from the list of grabbers
comp.removeAncestorListener(this);
_chatGrabbers.remove(comp);
// update the current chat grabbing component
_curChatGrabber = _chatGrabbers.isEmpty() ? null :
_chatGrabbers.getLast();
}
/**
* Adds the key listener to receive all key events at all times.
*/
public void addGlobalKeyListener (KeyListener listener)
{
_listeners.add(listener);
}
/**
* Removes the specified global key listener.
*/
public void removeGlobalKeyListener (KeyListener listener)
{
_listeners.remove(listener);
}
// documentation inherited from interface KeyEventDispatcher
public boolean dispatchKeyEvent (KeyEvent e)
{
int lsize = _listeners.size();
switch (e.getID()) {
case KeyEvent.KEY_TYPED:
// dispatch to all the global listeners
for (int ii = 0; ii < lsize; ii++) {
_listeners.get(ii).keyTyped(e);
}
// see if a chat grabber needs to grab it
if (_curChatGrabber != null) {
Component target = e.getComponent();
// if the key was typed on a non-text component or one
// that wasn't editable...
if (isChatCharacter(e.getKeyChar()) &&
!isTypableTarget(target)) {
// focus our grabby component, and redirect this
// key event there
_curChatGrabber.requestFocusInWindow();
KeyboardFocusManager.getCurrentKeyboardFocusManager().
redispatchEvent(_curChatGrabber, e);
return true;
}
}
break;
case KeyEvent.KEY_PRESSED:
if (lsize > 0) {
for (int ii = 0; ii < lsize; ii++) {
_listeners.get(ii).keyPressed(e);
}
// remember the key event..
_downKeys.put(e.getKeyCode(), e);
}
break;
case KeyEvent.KEY_RELEASED:
if (lsize > 0) {
for (int ii = 0; ii < lsize; ii++) {
((KeyListener) _listeners.get(ii)).keyReleased(e);
}
// forget the key event
_downKeys.remove(e.getKeyCode());
}
break;
}
return false;
}
/**
* Returns true if the specified target component supports being typed
* into, and thus we shouldn't steal focus away from it if the user
* starts typing.
*/
protected boolean isTypableTarget (Component target)
{
return target.isShowing() &&
(((target instanceof JTextComponent) &&
((JTextComponent) target).isEditable()) ||
(target instanceof JTable) ||
(target instanceof JRootPane));
}
/**
* Returns whether the specified character is a chat character.
*/
protected boolean isChatCharacter (char c)
{
return (Character.isLetterOrDigit(c) || ('/' == c));
}
// documentation inherited from interface WindowFocusListener
public void windowGainedFocus (WindowEvent e)
{
// nothing
}
// documentation inherited from interface WindowFocusListener
public void windowLostFocus (WindowEvent e)
{
// un-press any keys that were left down
if (!_downKeys.isEmpty()) {
long now = System.currentTimeMillis();
for (KeyEvent down : _downKeys.values()) {
KeyEvent up = new KeyEvent(
down.getComponent(), KeyEvent.KEY_RELEASED, now,
down.getModifiers(), down.getKeyCode(), down.getKeyChar(),
down.getKeyLocation());
for (int ii = 0, nn = _listeners.size(); ii < nn; ii++) {
_listeners.get(ii).keyReleased(up);
}
}
_downKeys.clear();
}
}
// documentation inherited from interface AncestorListener
public void ancestorAdded (AncestorEvent ae)
{
// nothing
}
// documentation inherited from interface AncestorListener
public void ancestorMoved (AncestorEvent ae)
{
// nothing
}
// documentation inherited from interface AncestorListener
public void ancestorRemoved (AncestorEvent ae)
{
removeChatGrabber((JTextComponent) ae.getComponent());
}
/** The main window for which we're observing key events. */
protected Window _window;
/** The current most-recently pushed component that wants to grab
* alphanumeric key presses. */
protected JTextComponent _curChatGrabber;
/** The stack of grabbers. */
protected LinkedList<JTextComponent> _chatGrabbers =
new LinkedList<JTextComponent>();
/** Global key listeners. */
protected ArrayList<KeyListener> _listeners = new ArrayList<KeyListener>();
/** Keys that are currently held down. */
protected HashIntMap<KeyEvent> _downKeys = new HashIntMap<KeyEvent>();
}
@@ -0,0 +1,79 @@
//
// $Id: KeyTranslator.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.util.Iterator;
/**
* The key translator interface provides a means whereby the keyboard
* manager can map a key code to the logical {@link
* com.samskivert.swing.Controller} action command that it represents.
*/
public interface KeyTranslator
{
/**
* Returns whether there is an action command for the key
* corresponding to the given keycode. The translator may have an
* action command for either a key press or a key release of the key,
* or both.
*/
public boolean hasCommand (int keyCode);
/**
* Returns the action command string associated with a key press of
* the key corresponding to the given key code, or <code>null</code>
* if there is no associated command.
*/
public String getPressCommand (int keyCode);
/**
* Returns the action command string associated with a key release of
* the key corresponding to the given key code, or <code>null</code>
* if there is no associated command.
*/
public String getReleaseCommand (int keyCode);
/**
* Returns the number of times each second that key presses are to be
* automatically repeated while the key is held down, or
* <code>0</code> to disable auto-repeat for the key.
*/
public int getRepeatRate (int keyCode);
/**
* Returns the delay in milliseconds before generating auto-repeated
* key press events for the specified key.
*/
public long getRepeatDelay (int keyCode);
/**
* Returns an iterator that iterates over the available press
* commands.
*/
public Iterator enumeratePressCommands ();
/**
* Returns an iterator that iterates over the available release
* commands.
*/
public Iterator enumerateReleaseCommands ();
}
@@ -0,0 +1,190 @@
//
// $Id: KeyTranslatorImpl.java 3272 2004-12-13 07:52:09Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.util.ArrayList;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
/**
* A basic implementation of the {@link KeyTranslator} interface that
* provides facilities for mapping key codes to action command strings for
* use by the {@link KeyboardManager}.
*/
public class KeyTranslatorImpl implements KeyTranslator
{
/**
* Adds a mapping from a key press to an action command string that
* will auto-repeat at a default repeat rate.
*/
public void addPressCommand (int keyCode, String command)
{
addPressCommand(keyCode, command, DEFAULT_REPEAT_RATE);
}
/**
* Adds a mapping from a key press to an action command string that
* will auto-repeat at the specified repeat rate. Overwrites any
* existing mapping and repeat rate that may have already been
* registered.
*
* @param rate the number of times each second that the key press
* should be repeated while the key is down, or <code>0</code> to
* disable auto-repeat for the key.
*/
public void addPressCommand (int keyCode, String command, int rate)
{
addPressCommand(keyCode, command, rate, DEFAULT_REPEAT_DELAY);
}
/**
* Adds a mapping from a key press to an action command string that
* will auto-repeat at the specified repeat rate after the specified
* auto-repeat delay has expired. Overwrites any existing mapping for
* the specified key code that may have already been registered.
*
* @param rate the number of times each second that the key press
* should be repeated while the key is down; passing <code>0</code>
* will result in no repeating.
* @param repeatDelay the delay in milliseconds before auto-repeating
* key press events will be generated for the key.
*/
public void addPressCommand (
int keyCode, String command, int rate, long repeatDelay)
{
KeyRecord krec = getKeyRecord(keyCode);
krec.pressCommand = command;
krec.repeatRate = rate;
krec.repeatDelay = repeatDelay;
}
/**
* Adds a mapping from a key release to an action command string.
* Overwrites any existing mapping that may already have been
* registered.
*/
public void addReleaseCommand (int keyCode, String command)
{
KeyRecord krec = getKeyRecord(keyCode);
krec.releaseCommand = command;
}
/**
* Returns the key record for the specified key, creating it and
* inserting it in the key table if necessary.
*/
protected KeyRecord getKeyRecord (int keyCode)
{
KeyRecord krec = (KeyRecord)_keys.get(keyCode);
if (krec == null) {
krec = new KeyRecord();
_keys.put(keyCode, krec);
}
return krec;
}
// documentation inherited
public boolean hasCommand (int keyCode)
{
return (_keys.get(keyCode) != null);
}
// documentation inherited
public String getPressCommand (int keyCode)
{
KeyRecord krec = (KeyRecord)_keys.get(keyCode);
return (krec == null) ? null : krec.pressCommand;
}
// documentation inherited
public String getReleaseCommand (int keyCode)
{
KeyRecord krec = (KeyRecord)_keys.get(keyCode);
return (krec == null) ? null : krec.releaseCommand;
}
// documentation inherited
public int getRepeatRate (int keyCode)
{
KeyRecord krec = (KeyRecord)_keys.get(keyCode);
return (krec == null) ? DEFAULT_REPEAT_RATE : krec.repeatRate;
}
// documentation inherited
public long getRepeatDelay (int keyCode)
{
KeyRecord krec = (KeyRecord)_keys.get(keyCode);
return (krec == null) ? DEFAULT_REPEAT_DELAY : krec.repeatDelay;
}
// documentation inherited
public Iterator enumeratePressCommands ()
{
ArrayList commands = new ArrayList();
Iterator iter = _keys.values().iterator();
while (iter.hasNext()) {
KeyRecord krec = (KeyRecord)iter.next();
commands.add(krec.pressCommand);
}
return commands.iterator();
}
// documentation inherited
public Iterator enumerateReleaseCommands ()
{
ArrayList commands = new ArrayList();
Iterator iter = _keys.values().iterator();
while (iter.hasNext()) {
KeyRecord krec = (KeyRecord)iter.next();
commands.add(krec.releaseCommand);
}
return commands.iterator();
}
protected static class KeyRecord
{
/** The command to be posted when the key is pressed. */
public String pressCommand;
/** The command to be posted when the key is released. */
public String releaseCommand;
/** The rate in presses per second at which the key is to be
* auto-repeated. */
public int repeatRate;
/** The delay in milliseconds that must expire with the key still
* pressed before auto-repeated key presses will begin. */
public long repeatDelay;
}
/** The keys for which commands are registered. */
protected HashIntMap _keys = new HashIntMap();
/** The default key press repeat rate. */
protected static final int DEFAULT_REPEAT_RATE = 5;
/** The default delay in milliseconds before auto-repeated key presses
* will begin. */
protected static final long DEFAULT_REPEAT_DELAY = 500L;
}
@@ -0,0 +1,720 @@
//
// $Id: KeyboardManager.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.Iterator;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import com.samskivert.swing.Controller;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Interval;
import com.samskivert.util.ObserverList;
import com.samskivert.util.RunAnywhere;
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
* {@link Controller} hierarchy. It allows specifying the key repeat
* rate, and will begin repeating a key immediately after it is held down
* rather than depending on the system-specific key repeat delay/rate.
*/
public class KeyboardManager
implements KeyEventDispatcher, AncestorListener, WindowFocusListener
{
/**
* An interface to be implemented by those that care to be notified
* whenever an event (either a key press or a key release) occurs for
* any key while the keyboard manager is active. We use this custom
* interface rather than the more standard {@link
* java.awt.event.KeyListener} interface so that we needn't create key
* pressed and released event objects each time a (potentially
* artificially-generated) event occurs.
*/
public interface KeyObserver
{
/**
* Called whenever a key event occurs for a particular key.
*/
public void handleKeyEvent (int id, int keyCode, long timestamp);
}
/**
* Constructs a keyboard manager that is initially disabled. The
* keyboard manager should not be enabled until it has been supplied
* with a target component and translator via {@link #setTarget}.
*/
public KeyboardManager ()
{
// capture low-level keyboard events via the keyboard focus manager
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addKeyEventDispatcher(this);
}
/**
* Resets the keyboard manager, clearing any target and key translator
* in use and disabling the keyboard manager if it is currently
* active.
*/
public void reset ()
{
setEnabled(false);
_target = null;
_xlate = null;
_focus = false;
}
/**
* Initializes the keyboard manager with the supplied target component
* and key translator and disables the keyboard manager if it is
* currently active.
*
* @param target the component whose keyboard events are to be observed.
* @param xlate the key translator used to map keyboard events to
* controller action commands.
*/
public void setTarget (JComponent target, KeyTranslator xlate)
{
setEnabled(false);
// save off references
_target = target;
_xlate = xlate;
}
/**
* Registers a key observer that will be notified of all key events
* while the keyboard manager is active.
*/
public void registerKeyObserver (KeyObserver obs)
{
_observers.add(obs);
}
/**
* Removes the supplied key observer from the list of observers to be
* notified of all key events while the keyboard manager is active.
*/
public void removeKeyObserver (KeyObserver obs)
{
_observers.remove(obs);
}
/**
* Sets whether the keyboard manager processes keyboard input.
*/
public void setEnabled (boolean enabled)
{
// report incorrect usage
if (enabled && _target == null) {
Log.warning("Attempt to enable uninitialized keyboard manager!");
Thread.dumpStack();
return;
}
// ignore NOOPs
if (enabled == _enabled) {
return;
}
if (!enabled) {
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 {
// listen to ancestor events so that we can cease our business
// if we lose the focus
_target.addAncestorListener(this);
// if we're already showing, listen to window focus events,
// else we have to wait until the target is added since it
// doesn't currently have a window
if (_target.isShowing() && _window == null) {
_window = SwingUtilities.getWindowAncestor(_target);
if (_window != null) {
_window.addWindowFocusListener(this);
}
}
// 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
_enabled = enabled;
}
/**
* Sets the expected delay in milliseconds between each key
* press/release event the keyboard manager should expect to receive
* while a key is repeating.
*/
public void setRepeatDelay (long delay)
{
_repeatDelay = delay;
}
/**
* Releases all keys and ceases any hot repeating action that may be
* going on.
*/
public void releaseAllKeys ()
{
long now = System.currentTimeMillis();
Iterator iter = _keys.elements();
while (iter.hasNext()) {
((KeyInfo)iter.next()).release(now);
}
}
/**
* Called when the keyboard manager gains focus and should begin
* handling keys again if it was previously enabled.
*/
protected void gainedFocus ()
{
if (Keyboard.isAvailable()) {
// disable key auto-repeating
Keyboard.setKeyRepeat(false);
}
// note that we've regained the focus
_focus = true;
}
/**
* Called when the keyboard manager loses focus and should cease
* handling keys.
*/
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;
}
// documentation inherited from interface KeyEventDispatcher
public boolean dispatchKeyEvent (KeyEvent e)
{
// bail if we're not enabled, we haven't the focus, or we're not
// showing on-screen
if (!_enabled || !_focus || !_target.isShowing()) {
// Log.info("dispatchKeyEvent [enabled=" + _enabled +
// ", focus=" + _focus +
// ", showing=" + ((_target == null) ? "N/A" :
// "" + _target.isShowing()) + "].");
return false;
}
// handle key press and release events
switch (e.getID()) {
case KeyEvent.KEY_PRESSED:
return keyPressed(e);
case KeyEvent.KEY_RELEASED:
return keyReleased(e);
default:
return false;
}
}
/**
* Called when Swing notifies us that a key has been pressed while the
* keyboard manager is active.
*
* @return true to swallow the key event
*/
protected boolean keyPressed (KeyEvent e)
{
logKey("keyPressed", e);
// get the action command associated with this key
int keyCode = e.getKeyCode();
boolean hasCommand = _xlate.hasCommand(keyCode);
if (hasCommand) {
// get the info object for this key, creating one if necessary
KeyInfo info = (KeyInfo)_keys.get(keyCode);
if (info == null) {
info = new KeyInfo(keyCode);
_keys.put(keyCode, info);
}
// remember the last time this key was pressed
info.setPressTime(RunAnywhere.getWhen(e));
}
// notify any key observers of the key press
notifyObservers(KeyEvent.KEY_PRESSED, e.getKeyCode(),
RunAnywhere.getWhen(e));
return hasCommand;
}
/**
* Called when Swing notifies us that a key has been released while
* the keyboard manager is active.
*
* @return true to swallow the key event
*/
protected boolean keyReleased (KeyEvent e)
{
logKey("keyReleased", e);
// get the info object for this key
KeyInfo info = (KeyInfo)_keys.get(e.getKeyCode());
if (info != null) {
// remember the last time we received a key release
info.setReleaseTime(RunAnywhere.getWhen(e));
}
// notify any key observers of the key release
notifyObservers(KeyEvent.KEY_RELEASED, e.getKeyCode(),
RunAnywhere.getWhen(e));
return (info != null);
}
/**
* Notifies all registered key observers of the supplied key event.
* This method provides a thread-safe manner in which to notify the
* observers, which is necessary since the {@link KeyInfo} objects do
* various antics from the interval manager thread whilst we may do
* other notification from the AWT thread when normal key events are
* handled.
*/
protected synchronized void notifyObservers (
int id, int keyCode, long timestamp)
{
_keyOp.init(id, keyCode, timestamp);
_observers.apply(_keyOp);
}
/**
* Logs the given message and key.
*/
protected void logKey (String msg, KeyEvent e)
{
if (DEBUG_EVENTS) {
int keyCode = e.getKeyCode();
Log.info(msg + " [key=" + KeyEvent.getKeyText(keyCode) + "].");
}
}
// documentation inherited from interface AncestorListener
public void ancestorAdded (AncestorEvent e)
{
gainedFocus();
if (_window == null) {
_window = SwingUtilities.getWindowAncestor(_target);
_window.addWindowFocusListener(this);
}
}
// documentation inherited from interface AncestorListener
public void ancestorMoved (AncestorEvent e)
{
// nothing for now
}
// documentation inherited from interface AncestorListener
public void ancestorRemoved (AncestorEvent e)
{
lostFocus();
if (_window != null) {
_window.removeWindowFocusListener(this);
_window = null;
}
}
// documentation inherited from interface WindowFocusListener
public void windowGainedFocus (WindowEvent e)
{
gainedFocus();
}
// documentation inherited from interface WindowFocusListener
public void windowLostFocus (WindowEvent e)
{
lostFocus();
}
protected class KeyInfo extends Interval
{
/**
* Constructs a key info object for the given key code.
*/
public KeyInfo (int keyCode)
{
_keyCode = keyCode;
_keyText = KeyEvent.getKeyText(_keyCode);
_pressCommand = _xlate.getPressCommand(_keyCode);
_releaseCommand = _xlate.getReleaseCommand(_keyCode);
int rate = _xlate.getRepeatRate(_keyCode);
_pressDelay = (rate == 0) ? 0 : (1000L / rate);
_repeatDelay = _xlate.getRepeatDelay(_keyCode);
}
/**
* Sets the last time the key was pressed.
*/
public synchronized void setPressTime (long time)
{
if (_lastPress == 0 && _pressCommand != null) {
// post the initial key press command
postPress(time);
}
if (!_scheduled && _pressDelay > 0) {
// register an interval to post the key press command
// until the key is decidedly released
if (_repeatDelay > 0) {
schedule(_repeatDelay, _pressDelay);
} else {
schedule(_pressDelay, true);
}
_scheduled = true;
if (DEBUG_EVENTS) {
Log.info("Pressing key [key=" + _keyText + "].");
}
}
_lastPress = time;
_lastRelease = time;
}
/**
* Sets the last time the key was released.
*/
public synchronized void setReleaseTime (long time)
{
release(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.
// 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);
}
}
/**
* Releases the key if pressed and cancels any active key repeat
* interval.
*/
public synchronized void release (long timestamp)
{
// bail if we're not currently pressed
if (_lastPress == 0) {
return;
}
if (DEBUG_EVENTS) {
Log.info("Releasing key [key=" + _keyText + "].");
}
// remove the repeat interval
if (_scheduled) {
cancel();
_scheduled = false;
}
if (_releaseCommand != null) {
// post the key release command
postRelease(timestamp);
}
// clear out the last press and release timestamps
_lastPress = _lastRelease = 0;
}
// documentation inherited
public synchronized void expired ()
{
long now = System.currentTimeMillis();
long deltaPress = now - _lastPress;
long deltaRelease = now - _lastRelease;
if (KeyboardManager.DEBUG_INTERVAL) {
Log.info("Interval [key=" + _keyText +
", deltaPress=" + deltaPress +
", deltaRelease=" + deltaRelease + "].");
}
// handle a normal interval where we either (a) create a
// sub-interval if we can't yet determine definitively
// whether the key is still down, (b) cease repeating if
// 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, Long.valueOf(_lastPress), false);
// if (KeyboardManager.DEBUG_INTERVAL) {
// Log.info("Registered sub-interval " +
// "[id=" + _siid + "].");
// }
// } else {
// we know the key was released, so cease repeating
release(now);
// }
} else if (_lastPress != 0 && _pressCommand != null) {
// post the key press command again
postPress(now);
}
}
/*
* Old stuff- sub interval stuff was commented out prior to my
* reworking of Interval, I'll be damned if I'm going to convert this
* code that wasn't even being used.
} else if (id == _siid) {
// handle the sub-interval that checks whether the key has
// really been released since the normal interval expired
// at an inopportune time for a definitive check
// clear out the non-recurring sub-interval identifier
_siid = -1;
// make sure the key hasn't been pressed again since the
// sub-interval was registered
if (_lastPress != ((Long)arg).longValue()) {
if (KeyboardManager.DEBUG_INTERVAL) {
Log.warning("Key pressed since sub-interval was " +
"registered, aborting release check " +
"[key=" + _keyText + "].");
}
return;
}
// provide the last word on whether the key was released
if ((_lastRelease != _lastPress) &&
deltaRelease >= _repeatDelay) {
release(now);
} else if (_pressCommand != null) {
// post the key command again
postPress(now);
}
}
}
**/
/**
* Posts the press command for this key and notifies all key
* observers of the key press.
*/
protected void postPress (long timestamp)
{
notifyObservers(KeyEvent.KEY_PRESSED, _keyCode, timestamp);
Controller.postAction(_target, _pressCommand);
}
/**
* Posts the release command for this key and notifies all key
* observers of the key release.
*/
protected void postRelease (long timestamp)
{
notifyObservers(KeyEvent.KEY_RELEASED, _keyCode, timestamp);
Controller.postAction(_target, _releaseCommand);
}
/** Returns a string representation of the key info object. */
public String toString ()
{
return "[key=" + _keyText + "]";
}
/** True if we are a scheduled interval. */
protected boolean _scheduled = false;
/** The last time a key released event was received for this key. */
protected long _lastRelease;
/** The last time a key pressed event was received for this key. */
protected long _lastPress;
/** The press action command associated with this key. */
protected String _pressCommand;
/** The release action command associated with this key. */
protected String _releaseCommand;
/** A text representation of this key. */
protected String _keyText;
/** The key code associated with this key info object. */
protected int _keyCode;
/** The milliseconds to sleep between sending repeat key commands. */
protected long _pressDelay;
/** The delay in milliseconds before auto-repeating the key press. */
protected long _repeatDelay;
}
/** An observer operation to notify observers of a key event. */
protected static class KeyObserverOp implements ObserverList.ObserverOp
{
/** Initialized the operation with its parameters. */
public void init (int id, int keyCode, long timestamp)
{
_id = id;
_keyCode = keyCode;
_timestamp = timestamp;
}
// documentation inherited from interface ObserverList.ObserverOp
public boolean apply (Object observer)
{
((KeyObserver)observer).handleKeyEvent(_id, _keyCode, _timestamp);
return true;
}
/** The key event id. */
protected int _id;
/** The key code. */
protected int _keyCode;
/** The key event timestamp. */
protected long _timestamp;
}
/** Whether to output debugging info for individual key events. */
protected static final boolean DEBUG_EVENTS = false;
/** Whether to output debugging info for interval callbacks. */
protected static final boolean DEBUG_INTERVAL = false;
/** The default repeat delay. */
protected static final long DEFAULT_REPEAT_DELAY = 50L;
/** The expected approximate milliseconds between each key
* release/press event while the key is being auto-repeated. */
protected long _repeatDelay = DEFAULT_REPEAT_DELAY;
/** A hashtable mapping key codes to {@link KeyInfo} objects. */
protected HashIntMap _keys = new HashIntMap();
/** Whether the keyboard manager currently has the keyboard focus. */
protected boolean _focus;
/** Whether the keyboard manager is accepting keyboard input. */
protected boolean _enabled;
/** The window containing our target component whose focus events we
* care to observe, or null if we're not observing a window. */
protected Window _window;
/** The component that receives keyboard events and that we associate
* with posted controller commands. */
protected JComponent _target;
/** The translator that maps keyboard events to controller commands. */
protected KeyTranslator _xlate;
/** The list of key observers. */
protected ObserverList _observers =
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
/** 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;
}
+56
View File
@@ -0,0 +1,56 @@
//
// $Id: Log.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util;
/**
* A placeholder class that contains a reference to the log object used by
* the media services package.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("util");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,83 @@
//
// $Id: Keyboard.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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;
}
/**
* Initializes the library and returns true if it successfully did so.
*/
protected static native boolean init ();
/** Whether the keyboard native library was successfully loaded. */
protected static boolean _haveLib;
static {
try {
System.loadLibrary("keybd");
_haveLib = init();
if (_haveLib) {
Log.info("Loaded native keyboard library.");
} else {
Log.info("Native keyboard library initialization failed.");
}
} catch (UnsatisfiedLinkError e) {
Log.warning("Failed to load native keyboard library " +
"[e=" + e + "].");
_haveLib = false;
}
}
}
@@ -0,0 +1,44 @@
#
# $Id: Makefile 3330 2005-02-03 01:24:57Z mdb $
#
# Executable definitions
CC=gcc
RM=rm
CP=cp
MKDIR=mkdir
#
# Directory definitions
ROOT=../../../../../../..
LIBRARIES_PATH=-L/usr/X11R6/lib
INSTALL_PATH=${ROOT}/dist/lib/i686-Linux
#
# Parameter and file definitions
INCLUDES=-I.. -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux
LIBRARIES=-lX11
TARGET=libkeybd.so
#
# Target definitions
all: ${TARGET}
install: ${TARGET}
@${MKDIR} -p ${INSTALL_PATH}
cp ${TARGET} ${INSTALL_PATH}
${TARGET}: com_threerings_util_keybd_Keyboard.c
@echo Compiling Keyboard.c
@${CC} ${INCLUDES} -c com_threerings_util_keybd_Keyboard.c \
-o com_threerings_util_keybd_Keyboard.o
@echo Creating libkeybd.so
@${CC} -o ${TARGET} com_threerings_util_keybd_Keyboard.o \
${LIBRARIES} ${LIBRARIES_PATH} -shared
clean:
-${RM} -f *.o ${TARGET}
@@ -0,0 +1,87 @@
/*
* $Id: com_threerings_util_keybd_Keyboard.c 3330 2005-02-03 01:24:57Z mdb $
*/
#include <stdio.h>
#include <X11/Xlib.h>
#include <jni.h>
#include "com_threerings_util_keybd_Keyboard.h"
/* defines */
#define MESSAGE_LENGTH (256)
/* prototype definitions */
Display* getXDisplay (JNIEnv* env);
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_keybd_Keyboard_init (
JNIEnv* env, jclass class, jboolean enabled)
{
Display* display = getXDisplay(env);
if (display == NULL) {
/* If we are unable to open a display, we can't function. */
return JNI_FALSE;
} else {
XCloseDisplay(display);
return JNI_TRUE;
}
}
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));
return NULL;
}
/* printf("Opened display [disp=%s].\n", XDisplayName(disp)); */
return dpy;
}
Binary file not shown.
@@ -0,0 +1,7 @@
#
# $Id: Makefile 3331 2005-02-03 01:25:21Z mdb $
#
# Placeholder Makefile
install:
@echo Nothing to see here. Move it along.
@@ -0,0 +1 @@
keybd.dll
@@ -0,0 +1,27 @@
#
# $Id: Makefile 3330 2005-02-03 01:24:57Z mdb $
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}
@@ -0,0 +1,104 @@
/*
* $Id: com_threerings_util_keybd_Keyboard.c 3330 2005-02-03 01:24:57Z mdb $
*/
#include <stdio.h>
#include <jni.h>
#include <windows.h>
#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
@@ -0,0 +1,29 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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
@@ -0,0 +1,45 @@
#
# $Id: Makefile 3332 2005-02-03 01:30:33Z mdb $
#
# Executable definitions
CC=gcc
RM=rm
CP=cp
MKDIR=mkdir
#
# Directory definitions
ROOT=../../../../../../..
LIBRARIES_PATH=
OSINCDIR!=uname -s | tr 'A-Z' 'a-z'
INSTALL_PATH=${ROOT}/dist/lib/i386-FreeBSD
#
# Parameter and file definitions
INCLUDES=-I.. -I${JAVA_HOME}/include -I${JAVA_HOME}/include/${OSINCDIR}
LIBRARIES=
TARGET=libunsafe.so
#
# Target definitions
all: ${TARGET}
install: ${TARGET}
@${MKDIR} -p ${INSTALL_PATH}
cp ${TARGET} ${INSTALL_PATH}
${TARGET}: com_threerings_util_unsafe_Unsafe.c
@echo "Compiling Unsafe.c"
@${CC} ${INCLUDES} -c com_threerings_util_unsafe_Unsafe.c \
-o com_threerings_util_unsafe_Unsafe.o
@echo "Creating libunsafe.so"
@${CC} -o ${TARGET} com_threerings_util_unsafe_Unsafe.o \
${LIBRARIES} ${LIBRARIES_PATH} -shared
clean:
-${RM} -f *.o ${TARGET}
@@ -0,0 +1,97 @@
/*
* $Id: com_threerings_util_unsafe_Unsafe.c 3653 2005-07-21 19:10:08Z mdb $
*/
#include <stdio.h>
#include <jni.h>
#include <jvmpi.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "com_threerings_util_unsafe_Unsafe.h"
/* global jvmpi interface pointer */
static JVMPI_Interface* jvmpi;
/** A sleep method that uses select(). This seems to have about 10ms
* granularity where nanosleep() has about 20ms. Sigh. */
static int select_sleep (int millisecs)
{
fd_set dummy;
struct timeval toWait;
FD_ZERO(&dummy);
toWait.tv_sec = millisecs / 1000;
toWait.tv_usec = (millisecs % 1000) * 1000;
return select(0, &dummy, NULL, NULL, &toWait);
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_enableGC (JNIEnv* env, jclass clazz)
{
jvmpi->EnableGC();
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_disableGC (JNIEnv* env, jclass clazz)
{
jvmpi->DisableGC();
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSleep (
JNIEnv* env, jclass clazz, jint millis)
{
/* struct timespec tmspec; */
/* tmspec.tv_sec = millis/1000; */
/* tmspec.tv_nsec = (millis%1000)*1000000; */
/* if (nanosleep(&tmspec, NULL) < 0) { */
/* fprintf(stderr, "nanosleep() failed: %s\n", strerror(errno)); */
/* } */
if (select_sleep(millis) < 0) {
fprintf(stderr, "select_sleep() failed: %s\n", strerror(errno));
}
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSetuid (
JNIEnv* env, jclass clazz, jint uid)
{
if (setuid((uid_t)uid) != 0) {
fprintf(stderr, "setuid(%d) failed: %s\n", uid, strerror(errno));
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSetgid (
JNIEnv* env, jclass clazz, jint gid)
{
if (setgid((gid_t)gid) != 0) {
fprintf(stderr, "setgid(%d) failed: %s\n", gid, strerror(errno));
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_init (JNIEnv* env, jclass clazz)
{
JavaVM* jvm;
if ((*env)->GetJavaVM(env, &jvm) > 0) {
fprintf(stderr, "Failed to get JavaVM from env.\n");
return JNI_FALSE;
}
/* get jvmpi interface pointer */
if (((*jvm)->GetEnv(jvm, (void**)&jvmpi, JVMPI_VERSION_1)) < 0) {
fprintf(stderr, "Failed to get JVMPI from JavaVM.\n");
return JNI_FALSE;
}
return JNI_TRUE;
}
Binary file not shown.
@@ -0,0 +1,44 @@
#
# $Id: Makefile 3331 2005-02-03 01:25:21Z mdb $
#
# Executable definitions
CC=gcc
RM=rm
CP=cp
MKDIR=mkdir
#
# Directory definitions
ROOT=../../../../../../..
LIBRARIES_PATH=-L/usr/X11R6/lib
INSTALL_PATH=${ROOT}/dist/lib/i686-Linux
#
# Parameter and file definitions
INCLUDES=-I.. -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux
LIBRARIES=-lX11
TARGET=libunsafe.so
#
# Target definitions
all: ${TARGET}
install: ${TARGET}
@${MKDIR} -p ${INSTALL_PATH}
cp ${TARGET} ${INSTALL_PATH}
${TARGET}: com_threerings_util_unsafe_Unsafe.c
@echo Compiling Unsafe.c
@${CC} ${INCLUDES} -c com_threerings_util_unsafe_Unsafe.c \
-o com_threerings_util_unsafe_Unsafe.o
@echo Creating ${TARGET}
@${CC} -o ${TARGET} com_threerings_util_unsafe_Unsafe.o \
${LIBRARIES} ${LIBRARIES_PATH} -shared
clean:
-${RM} -f *.o ${TARGET}
@@ -0,0 +1,97 @@
/*
* $Id: com_threerings_util_unsafe_Unsafe.c 3653 2005-07-21 19:10:08Z mdb $
*/
#include <stdio.h>
#include <jni.h>
#include <jvmpi.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "com_threerings_util_unsafe_Unsafe.h"
/* global jvmpi interface pointer */
static JVMPI_Interface* jvmpi;
/** A sleep method that uses select(). This seems to have about 10ms
* granularity where nanosleep() has about 20ms. Sigh. */
static int select_sleep (int millisecs)
{
fd_set dummy;
struct timeval toWait;
FD_ZERO(&dummy);
toWait.tv_sec = millisecs / 1000;
toWait.tv_usec = (millisecs % 1000) * 1000;
return select(0, &dummy, NULL, NULL, &toWait);
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_enableGC (JNIEnv* env, jclass clazz)
{
jvmpi->EnableGC();
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_disableGC (JNIEnv* env, jclass clazz)
{
jvmpi->DisableGC();
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSleep (
JNIEnv* env, jclass clazz, jint millis)
{
/* struct timespec tmspec; */
/* tmspec.tv_sec = millis/1000; */
/* tmspec.tv_nsec = (millis%1000)*1000000; */
/* if (nanosleep(&tmspec, NULL) < 0) { */
/* fprintf(stderr, "nanosleep() failed: %s\n", strerror(errno)); */
/* } */
if (select_sleep(millis) < 0) {
fprintf(stderr, "select_sleep() failed: %s\n", strerror(errno));
}
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSetuid (
JNIEnv* env, jclass clazz, jint uid)
{
if (setuid((uid_t)uid) != 0) {
fprintf(stderr, "setuid(%d) failed: %s\n", uid, strerror(errno));
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSetgid (
JNIEnv* env, jclass clazz, jint gid)
{
if (setgid((gid_t)gid) != 0) {
fprintf(stderr, "setgid(%d) failed: %s\n", gid, strerror(errno));
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_init (JNIEnv* env, jclass clazz)
{
JavaVM* jvm;
if ((*env)->GetJavaVM(env, &jvm) > 0) {
fprintf(stderr, "Failed to get JavaVM from env.\n");
return JNI_FALSE;
}
/* get jvmpi interface pointer */
if (((*jvm)->GetEnv(jvm, (void**)&jvmpi, JVMPI_VERSION_1)) < 0) {
fprintf(stderr, "Failed to get JVMPI from JavaVM.\n");
return JNI_FALSE;
}
return JNI_TRUE;
}
Binary file not shown.
@@ -0,0 +1,7 @@
#
# $Id: Makefile 3331 2005-02-03 01:25:21Z mdb $
#
# Placeholder Makefile
install:
@echo Nothing to see here. Move it along.
@@ -0,0 +1,144 @@
//
// $Id: Unsafe.java 3653 2005-07-21 19:10:08Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util.unsafe;
import com.threerings.util.Log;
import com.samskivert.util.RunAnywhere;
/**
* A native library for doing unsafe things. Don't use this library. If
* you must ignore that warning, then be sure you use it sparingly and
* only in very well considered cases.
*/
public class Unsafe
{
/**
* Enables or disables garbage collection. <em>Warning:</em> you will
* be fucked if you leave it disabled for too long. Do not do this
* unless you are dang sure about what you're doing and are prepared
* to test your code on every platform this side of Nantucket.
*
* <p> Calls to this method do not nest. Regardless of how many times
* you disable GC, only one call is required to reenable it.
*/
public static void setGCEnabled (boolean enabled)
{
// we don't support nesting, NOOP if the state doesn't change
if (_loaded && enabled != _gcEnabled) {
if (_gcEnabled = enabled) {
enableGC();
} else {
disableGC();
}
}
}
/**
* Causes the current thread to block for the specified number of
* milliseconds. This exists primarily to work around the fact that on
* Linux, {@link Thread#sleep} is only accurate to around 12ms which
* is wholly unacceptable.
*/
public static void sleep (int millis)
{
if (_loaded && RunAnywhere.isLinux()) {
nativeSleep(millis);
} else {
try {
Thread.sleep(millis);
} catch (InterruptedException ie) {
Log.info("Thread.sleep(" + millis + ") interrupted.");
}
}
}
/**
* Sets the processes uid to the specified value.
*
* @return true if the uid was changed, false if we were unable to do so.
*/
public static boolean setuid (int uid)
{
if (_loaded && !RunAnywhere.isWindows()) {
return nativeSetuid(uid);
}
return false;
}
/**
* Sets the processes uid to the specified value.
*
* @return true if the uid was changed, false if we were unable to do so.
*/
public static boolean setgid (int gid)
{
if (_loaded && !RunAnywhere.isWindows()) {
return nativeSetgid(gid);
}
return false;
}
/**
* Reenable garbage collection after a call to {@link #disableGC}.
*/
protected static native void enableGC ();
/**
* Disables garbage collection.
*/
protected static native void disableGC ();
/**
* Sleeps the current thread for the specified number of milliseconds.
*/
protected static native void nativeSleep (int millis);
/**
* Calls through to the native OS system call to change our uid.
*/
protected static native boolean nativeSetuid (int uid);
/**
* Calls through to the native OS system call to change our gid.
*/
protected static native boolean nativeSetgid (int gid);
/**
* Called to initialize our library.
*/
protected static native boolean init ();
/** The current state of GC enablement. */
protected static boolean _gcEnabled = true;
/** Whether or not we were able to load and initialize our library. */
protected static boolean _loaded;
static {
try {
System.loadLibrary("unsafe");
_loaded = init();
} catch (UnsatisfiedLinkError e) {
Log.warning("Failed to load 'unsafe' library: " + e + ".");
}
}
}
@@ -0,0 +1 @@
*.def
@@ -0,0 +1,49 @@
#
# $Id: Makefile 3331 2005-02-03 01:25:21Z mdb $
NAME=unsafe
#
# Executable definitions
CC=i586-mingw32msvc-gcc
RM=rm
CP=cp
MKDIR=mkdir
DLLWRAP=i586-mingw32msvc-dllwrap
#
# Directory definitions
ROOT=../../../../../../..
INSTALL_PATH=${ROOT}/dist/lib/i686-Windows
#
# Source files
SRCS = com_threerings_util_unsafe_Unsafe.c
OBJS = ${SRCS:.c=.o}
#
# Parameter and file definitions
INCLUDES=
CFLAGS=-I.. -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux
LDFLAGS=-L/usr/X11R6/lib
LIBS=
TARGET=${NAME}.dll
INSTALL_TARGET=${INSTALL_PATH}/${TARGET}
#
# Target definitions
all: ${INSTALL_TARGET}
${INSTALL_TARGET}: ${OBJS}
@${MKDIR} -p ${INSTALL_PATH}
${DLLWRAP} --output-def ${NAME}.def --add-stdcall-alias \
-o ${INSTALL_TARGET} -s ${OBJS}
clean:
-${RM} ${OBJS}
-${RM} ${INSTALL_TARGET}
@@ -0,0 +1,52 @@
/*
* $Id: com_threerings_util_unsafe_Unsafe.c 3331 2005-02-03 01:25:21Z mdb $
*/
#include <stdio.h>
#include <jni.h>
#include <jvmpi.h>
#include "com_threerings_util_unsafe_Unsafe.h"
/* global jvmpi interface pointer */
static JVMPI_Interface* jvmpi;
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_enableGC (JNIEnv* env, jclass clazz)
{
fprintf(stderr, "Reenabling GC.\n");
jvmpi->EnableGC();
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_disableGC (JNIEnv* env, jclass clazz)
{
fprintf(stderr, "Disabling GC.\n");
jvmpi->DisableGC();
}
JNIEXPORT void JNICALL
Java_com_threerings_util_unsafe_Unsafe_nativeSleep (
JNIEnv* env, jclass clazz, jint millis)
{
/* not supported */
}
JNIEXPORT jboolean JNICALL
Java_com_threerings_util_unsafe_Unsafe_init (JNIEnv* env, jclass clazz)
{
JavaVM* jvm;
if ((*env)->GetJavaVM(env, &jvm) > 0) {
fprintf(stderr, "Failed to get JavaVM from env.\n");
return JNI_FALSE;
}
/* get jvmpi interface pointer */
if (((*jvm)->GetEnv(jvm, (void**)&jvmpi, JVMPI_VERSION_1)) < 0) {
fprintf(stderr, "Failed to get JVMPI from JavaVM.\n");
return JNI_FALSE;
}
return JNI_TRUE;
}
@@ -0,0 +1,47 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_threerings_util_unsafe_Unsafe */
#ifndef _Included_com_threerings_util_unsafe_Unsafe
#define _Included_com_threerings_util_unsafe_Unsafe
#ifdef __cplusplus
extern "C" {
#endif
/* Inaccessible static: _gcEnabled */
/* Inaccessible static: _loaded */
/*
* Class: com_threerings_util_unsafe_Unsafe
* Method: enableGC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_threerings_util_unsafe_Unsafe_enableGC
(JNIEnv *, jclass);
/*
* Class: com_threerings_util_unsafe_Unsafe
* Method: disableGC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_threerings_util_unsafe_Unsafe_disableGC
(JNIEnv *, jclass);
/*
* Class: com_threerings_util_unsafe_Unsafe
* Method: nativeSleep
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_threerings_util_unsafe_Unsafe_nativeSleep
(JNIEnv *, jclass, jint);
/*
* Class: com_threerings_util_unsafe_Unsafe
* Method: init
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_threerings_util_unsafe_Unsafe_init
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif