/**************************************************************************
* Copyright (c) 2004, 2005 by Chris Gray, /k/ Embedded Java Solutions. *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of /k/ Embedded Java Solutions nor the names of *
* other contributors may be used to endorse or promote products *
* derived from this software without specific prior written permission.*
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL /K/ EMBEDDED JAVA SOLUTIONS OR OTHER CONTRIBUTORS BE *
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR *
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF *
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, *
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE *
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN *
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
package com.acunia.wonka.rudolph;
import java.awt.Window;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.StringTokenizer;
/**
** Rudolph's internal event scanning thread.
*/
public final class Scanner implements Runnable {
/**
** Sequence number so we can distinguish instances of Haigha by name.
*/
private static int seq;
/**
** Thread priority when scanning input devices.
*/
private static final int poll_priority;
/**
** Reference to our colleague the Dispatcher.
*/
private static Dispatcher mainDispatcher;
/**
** Time to sleep between scans Determined by system property
** com.acunia.wonka.rudolph.poll.interval, default = 100.
*/
private static int sleep_millis;
/**
** The current root window.
*/
private static Window currentRoot;
/**
** A stack of Window objects - the top element is the ancestor of the current root.
*/
private static Stack rootStack;
/**
** Stack of Thread objects - the top element is the ancestor of the current dispatch thread.
** (Only used if event queueing is disabled).
*/
private static Stack threadStack;
/**
** The current dispatch thread.
** (Only used if event queueing is disabled).
*/
private static Thread currentDispatchThread;
/**
** The thread which performs the scanning.
*/
private Thread ourThread;
/**
** The name of this Scanner (and of the corresponding thread).
*/
private String name;
/**
**
*/
private Window root = null;
/**
** State of the scanning thread - takes one of the THREAD_xxx values below.
*/
private int threadState;
private static final int THREAD_UNSTARTED = 0;
private static final int THREAD_STARTING = 1;
private static final int THREAD_RUNNING = 2;
private static final int THREAD_STOPPING = 3;
private static final int THREAD_STOPPED = 4;
/**
** During class initialisation we set up the mouse device and create the
** master instance of this class. We also set up a VM shutdown routine to
** undo this work when the VM is shut down.
*/
static {
String mouse_device = getMouseDevice();
sleep_millis = Integer.getInteger("rudolph.poll.interval", 100).intValue();
poll_priority = Integer.getInteger("rudolph.poll.priority", 10).intValue();
init(mouse_device);
// Add a hook to restore keyboard settings, etc.
Runtime.getRuntime().addShutdownHook(
new Thread() {
public void run() {
shutdown();
}
});
}
/**
** Native part of the static initialiser.
*/
private native static void init(String mouse);
/**
** Set up the mouse or other pointing device.
** The information describing the pointing device is found in file
** 'device.config' in the system directory, in a line beginning
** 'attach-mouse-device'. Then next two tokens on this line are
** ignored, and the fourth token is used as a path to the pointing device.
** <p>
** If no such line is found in the 'device.config' file, system property
** 'rudolph.default.mouse.device' is used; if this is undefined then the
** path defaults to '/dev/ts'.
*/
private static String getMouseDevice() {
InputStream s = ClassLoader.getSystemResourceAsStream("device.config");
BufferedReader r = new BufferedReader(new InputStreamReader(s));
String line;
try {
while ((line = r.readLine()) != null) {
int start;
int end;
while ((start = line.indexOf(" ")) != -1) {
line = line.substring(0, start) + line.substring(start + 1);
}
while ((start = line.indexOf("( ")) != -1) {
end = line.indexOf(")", start + 2);
if (end > start) {
line = line.substring(0, start) + line.substring(end + 1);
}
else {
line = line.substring(0, start);
}
}
StringTokenizer t = new StringTokenizer(line);
while (t.hasMoreTokens()) {
String command = t.nextToken();
if (command.toLowerCase().equals("attach-mouse-device")) {
try {
t.nextToken(); // ignored
t.nextToken(); // ignored
String path = t.nextToken();
return path;
}
catch (NoSuchElementException e) {
System.err.println("attach-mouse-device : syntax is attach-mouse-device <family> <number> <path>");
}
catch (NumberFormatException nfe) {
System.err.println("attach-mouse-device : illegal device number");
}
}
}
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
return System.getProperty("rudolph.default.mouse.device", "/dev/ts");
}
/**
** Compute name of this instance. If this is the first instance to be
** created, also launch the scanning thread.
*/
private static synchronized void prepare(Scanner instance, Window root) {
instance.name = "Haigha";
if (seq > 0) {
instance.name += "-" + seq + "(" + root + ")";
}
seq += 1;
}
private Scanner() {}
public Scanner(Dispatcher d, Window w) {
if (mainDispatcher == null) {
mainDispatcher = d;
}
prepare(this, w);
root = w;
}
/**
** Make this root the current root, and push the prior current root onto
** the stack. If queueing is disabled, launch a new scanning thread
** (because the existing thread is our caller, and will be blocked).
*/
void push() {
if (rootStack == null) {
rootStack = new Stack();
}
else {
rootStack.push(currentRoot);
currentRoot = root;
}
if ("Haigha".equals(name) || !Dispatcher.queueing_enabled) {
threadState = THREAD_STARTING;
ourThread = new Thread(this, name);
ourThread.setPriority(poll_priority);
ourThread.setDaemon(false);
ourThread.start();
}
if (!Dispatcher.queueing_enabled) {
Dispatcher.pushThread(ourThread);
}
}
/**
** Pop the prior current root from the stack.
*/
void pop() {
currentRoot = (Window)rootStack.pop();
if (ourThread != null) {
threadState = THREAD_STOPPING;
ourThread.interrupt();
synchronized(this) {
while (threadState < THREAD_STOPPED) {
try {
wait(250);
}
catch (InterruptedException ie) {
}
}
}
}
if (!Dispatcher.queueing_enabled) {
Dispatcher.popThread();
}
}
/**
** Native code to drain the input devices, i.e. to read and discard all
** available events.
*/
public native static void drain();
/**
** Native code for the shutdown hook.
*/
native static void shutdown();
/**
** Native code to poll the input devices.
*/
private native void poll(Window root);
/**
** Scanner main loop.
** The input devices are polled at priority 'poll_priority'; this native
** code attempts to read as many events as possible from each device.
*/
public void run() {
threadState = THREAD_RUNNING;
try {
while(threadState == THREAD_RUNNING) {
try {
Thread.sleep(sleep_millis);
poll(currentRoot);
}
catch (InterruptedException ie) {
}
catch (Exception exc) {
try {
exc.printStackTrace();
}
catch (Exception exc2) {
}
}
catch (Error err) {
try {
err.printStackTrace();
}
finally {
throw err; // this will kill thread
}
}
}
}
finally {
synchronized(this) {
threadState = THREAD_STOPPED;
notifyAll();
}
}
}
}