I'm wondering if there's a decent method of differentiating between virtual (from AWT's Robot) and physical sources of KeyEvent objects?
I'm making a virtual keyboard, but would like the keyboard to disappear when/if the user uses a physical keyboard.
Below is an example that illustrates the problem I need to get around (while ignoring the other fluff of implementing an actual virtual keyboard). I'm trying to fix the if/else statement below the FIXME line (I realize that the logic there is not correct).
import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class RobotTest
{
public static void main( String[] args )
{
new RobotTest();
}
public RobotTest()
{
// Create a "virtual keyboard"
MyWindow window = new MyWindow();
window.setVisible( true );
// Event listener to differentiate between virtual and physical key events.
Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener()
{
@Override
public void eventDispatched( final AWTEvent e )
{
if (( e instanceof KeyEvent )
&& ( ( KeyEvent ) e ).getID()==KeyEvent.KEY_PRESSED
&& ( ( KeyEvent ) e ).getKeyCode()==KeyEvent.VK_A)
{
// FIXME: BELOW IS GUARANTEED FALSE
if ( ( ( KeyEvent ) e ).getSource() instanceof Robot)
{
System.out.println("FROM ROBOT");
}
else
{
System.out.println("FROM KEYBOARD");
}
}
}
}, AWTEvent.KEY_EVENT_MASK);
}
// prototype keyboard with an "A" key.
private class MyWindow extends JFrame
{
public MyWindow()
{
JPanel content = new JPanel();
content.setLayout( new BorderLayout() );
// Button that emulates pressing A
JButton button = new JButton( "A" );
button.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
try
{
Robot r = new Robot();
r.keyPress( KeyEvent.VK_A );
r.keyRelease( KeyEvent.VK_A );
}
catch( AWTException ex )
{
ex.printStackTrace();
}
}
});
content.add( button, BorderLayout.CENTER );
setContentPane( content );
setSize(50, 50);
}
}
}
So the route I ended up going with isn't guaranteed to solve the general problem of determining if a KeyEvent came from the Robot or a physical source, but it lets me detect if there was any input outside of my robot.
I simply made a queue of expected events from the Robot. Once an event is received, I check if the Robot had intended to press that key by polling the queue. This is pretty much guaranteed to detect if there was a physical event, but, due to synchronicity issues, isn't guaranteed to find the source of the KeyEvent. I've attached the (extremely simple) solution in case it's of use to anyone.
import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class RobotTest
{
// CHANGE #1: store a queue of virtual key presses.
public static Queue<Integer> robotKeys = new LinkedList<Integer>();
public static void main( String[] args )
{
new RobotTest();
}
public RobotTest()
{
// Create a "virtual keyboard"
MyWindow window = new MyWindow();
window.setVisible( true );
// Event listener to differentiate between virtual and physical key events.
Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener()
{
@Override
public void eventDispatched( final AWTEvent e )
{
if (( e instanceof KeyEvent )
&& ( ( KeyEvent ) e ).getID()==KeyEvent.KEY_PRESSED
&& ( ( KeyEvent ) e ).getKeyCode()==KeyEvent.VK_A)
{
// CHANGE #2: check the received event against the front of the queue.
Integer expectedRobotKey = robotKeys.poll();
if (expectedRobotKey != null && expectedRobotKey == ( ( KeyEvent ) e ).getKeyCode())
{
System.out.println("No physical input detected.");
}
else
{
System.out.println("Physical input detected.");
}
}
}
}, AWTEvent.KEY_EVENT_MASK);
}
// prototype keyboard with an "A" key.
private class MyWindow extends JFrame
{
public MyWindow()
{
JPanel content = new JPanel();
content.setLayout( new BorderLayout() );
// Button that emulates pressing A
JButton button = new JButton( "A" );
button.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
try
{
Robot r = new Robot();
// CHANGE #3: add events to the stored queue before sending them out.
robotKeys.add( KeyEvent.VK_A );
r.keyPress( KeyEvent.VK_A );
r.keyRelease( KeyEvent.VK_A );
}
catch( AWTException ex )
{
ex.printStackTrace();
}
}
});
content.add( button, BorderLayout.CENTER );
setContentPane( content );
setSize(50, 50);
}
}
}