com.sshtools.javardp.keymapping.KeyCode_FileBased Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rdp Show documentation
Show all versions of rdp Show documentation
Swing RDP component based on ProperJavaRDP with some modifications to allow multiple sessions within the same runtime, and to fix some bugs and inconsistencies with newer Windows versions.
The newest version!
/*
* Copyright (c) 2005 Propero Limited Purpose: Read and supply keymapping
* information from a file
*/
package com.sshtools.javardp.keymapping;
import java.awt.event.KeyEvent;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sshtools.javardp.Options;
public class KeyCode_FileBased {
public static final int DOWN = 1;
public static final int QUIETDOWN = 3;
public static final int QUIETUP = 2;
public static final int SCANCODE_EXTENDED = 0x80;
public static final int UP = 0;
static Logger logger = LoggerFactory.getLogger(KeyCode_FileBased.class);
public boolean capsLockDown = false;
public boolean useLockingKeyState = true;
protected Options options;
Vector keyMap = new Vector();
private boolean altQuiet = false;
private Hashtable keysCurrentlyDown = new Hashtable();
private boolean lastEventMatched = false;
private KeyEvent lastKeyEvent = null;
private int mapCode = -1;
/**
* Constructor for a keymap generated from a specified file, formatted in
* the manner of a file generated by the writeToFile method
*
* @param options options
* @param keyMapFile File containing keymap data
* @throws KeyMapException on error
*/
public KeyCode_FileBased(Options options, String keyMapFile) throws KeyMapException {
this.options = options;
InputStream in;
File file = new File(keyMapFile);
try {
in = new FileInputStream(file);
try {
readMapFile(file.toURI().toURL(), in);
} finally {
in.close();
}
} catch (IOException e) {
throw new KeyMapException("KeyMap file not found: " + keyMapFile);
}
}
public KeyCode_FileBased(Options options, URL base, InputStream fstream) throws IOException, KeyMapException {
this.options = options;
readMapFile(base, fstream);
}
/**
* Retrieve the scancode corresponding to the supplied character as defined
* within this object. Also update the mod array to hold any modifier keys
* that are required to send alongside it.
*
* @param c Character to obtain scancode for
* @param mod List of modifiers to be updated by method
* @return Scancode of supplied key
*/
public int charToScancode(char c, String[] mod) {
Iterator i = keyMap.iterator();
int smallestDist = -1;
MapDef best = null;
while (i.hasNext()) {
MapDef current = (MapDef) i.next();
if (current.appliesTo(c)) {
best = current;
}
}
if (best != null) {
if (best.isShiftDown())
mod[0] = "SHIFT";
else if (best.isCtrlDown() && best.isAltDown())
mod[0] = "ALTGR";
else
mod[0] = "NONE";
return best.getScancode();
} else
return -1;
}
/**
* Return a mapping definition associated with the supplied key event from
* within the list stored in this object.
*
* @param e Key event to retrieve a definition for
* @return Mapping definition for supplied keypress
*/
public MapDef getDef(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_RELEASED) {
MapDef def = (MapDef) keysCurrentlyDown.get(new Integer(e.getKeyCode()));
registerKeyEvent(e, def);
if (e.getID() == KeyEvent.KEY_RELEASED)
if (logger.isDebugEnabled())
logger.debug("Released: " + e.getKeyCode() + " returned scancode: "
+ ((def != null) ? "" + def.getScancode() : "null"));
return def;
}
updateCapsLock(e);
Iterator i = keyMap.iterator();
int smallestDist = -1;
MapDef best = null;
boolean noScanCode = !hasScancode(e.getKeyChar());
while (i.hasNext()) {
MapDef current = (MapDef) i.next();
boolean applies;
if ((e.getID() == KeyEvent.KEY_PRESSED)) {
applies = current.appliesToPressed(e);
} else if ((!lastEventMatched) && (e.getID() == KeyEvent.KEY_TYPED)) {
applies = current.appliesToTyped(e, capsLockDown);
} else
applies = false;
if (applies) {
int d = current.modifierDistance(e, capsLockDown);
if ((smallestDist == -1) || (d < smallestDist)) {
smallestDist = d;
best = current;
}
}
}
if (e.getID() == KeyEvent.KEY_PRESSED)
if (logger.isDebugEnabled())
logger.debug("Pressed: " + e.getKeyCode() + " returned scancode: "
+ ((best != null) ? "" + best.getScancode() : "null"));
if (e.getID() == KeyEvent.KEY_TYPED)
if (logger.isDebugEnabled())
logger.debug(
"Typed: " + e.getKeyChar() + " returned scancode: " + ((best != null) ? "" + best.getScancode() : "null"));
registerKeyEvent(e, best);
return best;
}
/**
* Construct a list of keystrokes needed to reproduce an AWT key event via
* RDP
*
* @param e Keyboard event to reproduce
* @return List of character pairs representing scancodes and key actions to
* send to server
*/
public String getKeyStrokes(KeyEvent e) {
String codes = "";
MapDef d = getDef(e);
if (d == null)
return "";
codes = stateChanges(e, d);
String type = "";
if (e.getID() == KeyEvent.KEY_RELEASED) {
if ((!options.isCapsSendsUpAndDown()) && (e.getKeyCode() == KeyEvent.VK_CAPS_LOCK)) {
if (logger.isDebugEnabled())
logger.debug("Sending CAPSLOCK toggle");
codes = "" + ((char) 0x3a) + ((char) DOWN) + ((char) 0x3a) + ((char) UP) + codes;
} else {
type = "" + ((char) UP);
codes = ((char) d.getScancode()) + type + codes;
}
} else {
if ((!options.isCapsSendsUpAndDown()) && (e.getKeyCode() == KeyEvent.VK_CAPS_LOCK)) {
if (logger.isDebugEnabled())
logger.debug("Sending CAPSLOCK toggle");
codes += "" + ((char) 0x3a) + ((char) DOWN) + ((char) 0x3a) + ((char) UP);
} else {
type = "" + ((char) DOWN);
codes += ((char) d.getScancode()) + type;
}
}
return codes;
}
/**
* Get the RDP code specifying the key map in use
*
* @return ID for current key map
*/
public int getMapCode() {
return mapCode;
}
/**
* Return a scancode for the supplied key event, from within the mapping
* definitions stored in this object.
*
* @param e Key event for which to determine a scancode
* @return Scancode for the supplied keypress, according to current mappings
*/
public int getScancode(KeyEvent e) {
String[] mod = { "" };
MapDef d = getDef(e);
if (d != null) {
return d.getScancode();
} else
return -1;
}
/**
* Retrieve the scancode corresponding to the supplied character as defined
* within this object. Also update the mod array to hold any modifier keys
* that are required to send alongside it.
*
* @param c Character to obtain scancode for
* @return Scancode of supplied key
*/
public boolean hasScancode(char c) {
if (c == KeyEvent.CHAR_UNDEFINED)
return false;
Iterator i = keyMap.iterator();
MapDef best = null;
while (i.hasNext()) {
MapDef current = (MapDef) i.next();
if (current.appliesTo(c)) {
best = current;
}
}
return (best != null);
}
/**
* Read in a keymap definition file and add mappings to internal keymap
*
* @param base base
* @param fstream Stream connected to keymap file
* @throws IOException on error
* @throws KeyMapException on error
*/
public void readMapFile(URL base, InputStream fstream) throws IOException, KeyMapException {
// logger.info("Stream-based keycode reader");
int lineNum = 0; // current line number being parsed
String line = ""; // contents of line being parsed
if (fstream == null)
throw new KeyMapException("Could not find specified keymap file");
boolean mapCodeSet = false;
try {
DataInputStream in = new DataInputStream(fstream);
if (in == null)
logger.warn("in == null");
while (in.available() != 0) {
lineNum++;
line = in.readLine();
char fc = 0x0;
if ((line != null) && (line.length() > 0))
fc = line.charAt(0);
// ignore blank and commented lines
if ((line != null) && line.startsWith("include ")) {
URL newUrl = new URL(base, line.substring(8));
InputStream incIn = newUrl.openStream();
try {
readMapFile(newUrl, incIn);
} finally {
incIn.close();
}
} else if ((line != null) && (line.length() > 0) && (fc != '#') && (fc != 'c')) {
keyMap.add(new MapDef(options, line)); // parse line into a
// MapDef
// object and add to list
} else if (fc == 'c') {
StringTokenizer st = new StringTokenizer(line);
String s = st.nextToken();
s = st.nextToken();
mapCode = Integer.decode(s).intValue();
mapCodeSet = true;
}
}
// Add a set of mappings for alphabet characters with ctrl and alt
// pressed
Vector newMap = new Vector();
Iterator i = keyMap.iterator();
while (i.hasNext()) {
MapDef current = (MapDef) i.next();
if (current.isCharacterDef()
&& !(current.isAltDown() || current.isCtrlDown() || current.isShiftDown() || current.isCapslockOn())) {
int code = getCodeFromAlphaChar(current.getKeyChar());
if (code > -1) {
newMap.add(new MapDef(options, code, 0, current.getScancode(), true, false, false, false));
newMap.add(new MapDef(options, code, 0, current.getScancode(), false, false, true, false));
}
}
}
// Commit added mapping definitions
keyMap.addAll(newMap);
in.close();
} catch (NumberFormatException nfEx) {
throw new KeyMapException("" + nfEx.getMessage() + " is not numeric at line " + lineNum);
} catch (NoSuchElementException nseEx) {
throw new KeyMapException("Not enough parameters in definition at line " + lineNum);
} catch (KeyMapException kmEx) {
kmEx.printStackTrace();
throw new KeyMapException("Error parsing keymap file: " + kmEx.getMessage() + " at line " + lineNum);
} catch (Exception e) {
logger.error(e.getClass().getName() + ": " + e.getMessage());
e.printStackTrace();
throw new KeyMapException(e.getClass().getName() + ": " + e.getMessage());
}
if (!mapCodeSet)
throw new KeyMapException("No map identifier found in file");
}
/**
* Construct a list of changes to key states in order to correctly send the
* key action jointly defined by the supplied key event and mapping
* definition.
*
* @param e Key event received by Java (defining current state)
* @param theDef Key mapping to define desired keypress on server end
* @return changes
*/
public String stateChanges(KeyEvent e, MapDef theDef) {
String changes = "";
final int SHIFT = 0;
final int CTRL = 1;
final int ALT = 2;
final int CAPSLOCK = 3;
int BEFORE = 0;
int AFTER = 1;
boolean[][] state = new boolean[4][2];
state[SHIFT][BEFORE] = e.isShiftDown();
state[SHIFT][AFTER] = theDef.isShiftDown();
state[CTRL][BEFORE] = e.isControlDown() || e.isAltGraphDown();
state[CTRL][AFTER] = theDef.isCtrlDown();
state[ALT][BEFORE] = e.isAltDown() || e.isAltGraphDown();
state[ALT][AFTER] = theDef.isAltDown();
updateCapsLock(e);
state[CAPSLOCK][BEFORE] = capsLockDown;
state[CAPSLOCK][AFTER] = theDef.isCapslockOn();
if (e.getID() == KeyEvent.KEY_RELEASED) {
AFTER = 0;
BEFORE = 1;
}
if ((e == null) || (theDef == null) || (!theDef.isCharacterDef()))
return "";
String up = "" + ((char) UP);
String down = "" + ((char) DOWN);
String quietup = up;
String quietdown = down;
quietup = "" + ((char) QUIETUP);
quietdown = "" + ((char) QUIETDOWN);
if (state[SHIFT][BEFORE] != state[SHIFT][AFTER]) {
if (state[SHIFT][BEFORE])
changes += ((char) 0x2a) + up;
else
changes += ((char) 0x2a) + down;
}
if (state[CTRL][BEFORE] != state[CTRL][AFTER]) {
if (state[CTRL][BEFORE])
changes += ((char) 0x1d) + up;
else
changes += ((char) 0x1d) + down;
}
if (options.isAltkeyQuiet()) {
if (state[ALT][BEFORE] != state[ALT][AFTER]) {
if (state[ALT][BEFORE])
changes += (char) 0x38 + quietup + ((char) 0x38) + quietdown + ((char) 0x38) + up;
else {
if (e.getID() == KeyEvent.KEY_RELEASED) {
altQuiet = true;
changes += ((char) 0x38) + quietdown;
} else {
altQuiet = false;
changes += ((char) 0x38) + down;
}
}
} else if (state[ALT][AFTER] && altQuiet) {
altQuiet = false;
changes += (char) 0x38 + quietup + ((char) 0x38) + quietdown + ((char) 0x38) + up + ((char) 0x38) + down;
}
} else {
if (state[ALT][BEFORE] != state[ALT][AFTER]) {
if (state[ALT][BEFORE])
changes += ((char) 0x38) + up;
else
changes += ((char) 0x38) + down;
}
}
if (state[CAPSLOCK][BEFORE] != state[CAPSLOCK][AFTER]) {
changes += ((char) 0x3a) + down + ((char) 0x3a) + up;
}
return changes;
}
/**
* Output key map definitions to a file as a series of single line text
* descriptions
*
* @param filename File in which to store definitions
*/
public void writeToFile(String filename) {
try {
FileOutputStream out = new FileOutputStream(filename);
PrintStream p = new PrintStream(out);
Iterator i = keyMap.iterator();
while (i.hasNext()) {
((MapDef) i.next()).writeToStream(p);
}
p.close();
} catch (Exception e) {
System.err.println("Error writing to file: " + e.getMessage());
}
}
/**
* Given an alphanumeric character, return an AWT keycode
*
* @param keyChar Alphanumeric character
* @return AWT keycode representing input character, -1 if character not
* alphanumeric
*/
private int getCodeFromAlphaChar(char keyChar) {
if (('a' <= keyChar) && (keyChar <= 'z')) {
return KeyEvent.VK_A + keyChar - 'a';
}
if (('A' <= keyChar) && (keyChar <= 'Z')) {
return KeyEvent.VK_A + keyChar - 'A';
}
return -1;
}
private void registerKeyEvent(KeyEvent e, MapDef m) {
if (e.getID() == KeyEvent.KEY_RELEASED) {
keysCurrentlyDown.remove(new Integer(e.getKeyCode()));
if ((!options.isCapsSendsUpAndDown()) && (e.getKeyCode() == KeyEvent.VK_CAPS_LOCK)) {
if (logger.isDebugEnabled())
logger.debug("Turning CAPSLOCK off - key release");
capsLockDown = false;
}
lastEventMatched = false;
}
if (e.getID() == KeyEvent.KEY_PRESSED) {
lastKeyEvent = e;
if (m != null)
lastEventMatched = true;
else
lastEventMatched = false;
if ((options.isCapsSendsUpAndDown()) && (e.getKeyCode() == KeyEvent.VK_CAPS_LOCK)) {
if (logger.isDebugEnabled())
logger.debug("Toggling CAPSLOCK");
capsLockDown = !capsLockDown;
} else if (e.getKeyCode() == KeyEvent.VK_CAPS_LOCK) {
if (logger.isDebugEnabled())
logger.debug("Turning CAPSLOCK on - key press");
capsLockDown = true;
}
}
if (lastKeyEvent != null && m != null && !keysCurrentlyDown.containsKey(new Integer(lastKeyEvent.getKeyCode()))) {
keysCurrentlyDown.put(new Integer(lastKeyEvent.getKeyCode()), m);
lastKeyEvent = null;
}
}
private void updateCapsLock(KeyEvent e) {
capsLockDown = e.getComponent().getToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy