/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_CIRCLE;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_CROSS;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_DOWN;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_HOLD;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_HOME;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_LEFT;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_LTRIGGER;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_NOTE;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_RIGHT;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_RTRIGGER;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_SCREEN;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_SELECT;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_SQUARE;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_START;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_TRIANGLE;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_UP;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_VOLDOWN;
import static jpcsp.HLE.modules.sceCtrl.PSP_CTRL_VOLUP;
import jpcsp.hardware.Audio;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.settings.Settings;
import jpcsp.HLE.Modules;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import org.apache.log4j.Logger;
import net.java.games.input.Component;
import net.java.games.input.ControllerEnvironment;
import net.java.games.input.Event;
import net.java.games.input.EventQueue;
import net.java.games.input.Component.Identifier;
import net.java.games.input.Component.POV;
import net.java.games.input.Component.Identifier.Axis;
import net.java.games.input.Controller.Type;
public class Controller {
public static Logger log = Logger.getLogger("controller");
private static Controller instance;
public static final byte analogCenter = (byte) 128;
// Left analog stick
private byte Lx = analogCenter;
private byte Ly = analogCenter;
// PSP emulator on PS3 can also provide the right analog stick
private byte Rx = analogCenter;
private byte Ry = analogCenter;
private int Buttons = 0;
private keyCode lastKey = keyCode.RELEASED;
private net.java.games.input.Controller inputController;
private HashMap<Component.Identifier, Integer> buttonComponents;
private Component.Identifier analogLXAxis = Component.Identifier.Axis.X;
private Component.Identifier analogLYAxis = Component.Identifier.Axis.Y;
private Component.Identifier analogRXAxis = null;
private Component.Identifier analogRYAxis = null;
private Component.Identifier digitalXAxis = null;
private Component.Identifier digitalYAxis = null;
private Component.Identifier povArrows = Component.Identifier.Axis.POV;
private static final float minimumDeadZone = 0.1f;
private boolean hasRightAnalogController = false;
private HashMap<keyCode, String> controllerComponents;
private HashMap<Integer, keyCode> keys;
public enum keyCode {
UP, DOWN, LEFT, RIGHT,
LANUP, LANDOWN, LANLEFT, LANRIGHT,
RANUP, RANDOWN, RANLEFT, RANRIGHT,
START, SELECT,
TRIANGLE, SQUARE, CIRCLE, CROSS, L1, R1, HOME, HOLD, VOLMIN, VOLPLUS,
SCREEN, MUSIC, RELEASED
};
private class RightAnalogControllerSettingsListener extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setHasRightAnalogController(value);
}
}
protected Controller(net.java.games.input.Controller inputController) {
this.inputController = inputController;
Settings.getInstance().registerSettingsListener("hasRightAnalogController", "hasRightAnalogController", new RightAnalogControllerSettingsListener());
}
private void init() {
keys = new HashMap<Integer, keyCode>(22);
controllerComponents = new HashMap<keyCode, String>(22);
loadKeyConfig();
loadControllerConfig();
}
public static boolean isKeyboardController(net.java.games.input.Controller inputController) {
return inputController == null || inputController.getType() == Type.KEYBOARD;
}
public static Controller getInstance() {
if (instance == null) {
// Disable JInput messages sent to stdout...
java.util.logging.Logger.getLogger("net.java.games.input.DefaultControllerEnvironment").setLevel(Level.WARNING);
ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment();
net.java.games.input.Controller[] controllers = ce.getControllers();
net.java.games.input.Controller inputController = null;
// Reuse the controller from the settings
String controllerName = Settings.getInstance().readString("controller.controllerName");
// The controllerNameIndex is the index when several controllers have
// the same name. 0 to use the first controller with the given name,
// 1, to use the second...
int controllerNameIndex = Settings.getInstance().readInt("controller.controllerNameIndex", 0);
if (controllerName != null) {
for (int i = 0; controllers != null && i < controllers.length; i++) {
if (controllerName.equals(controllers[i].getName())) {
inputController = controllers[i];
if (controllerNameIndex <= 0) {
break;
}
controllerNameIndex--;
}
}
}
if (inputController == null) {
// Use the first KEYBOARD controller
for (int i = 0; controllers != null && i < controllers.length; i++) {
if (isKeyboardController(controllers[i])) {
inputController = controllers[i];
break;
}
}
}
if (inputController == null) {
log.info(String.format("No KEYBOARD controller found"));
for (int i = 0; controllers != null && i < controllers.length; i++) {
log.info(String.format(" Controller: '%s'", controllers[i].getName()));
}
} else {
log.info(String.format("Using default controller '%s'", inputController.getName()));
}
instance = new Controller(inputController);
instance.init();
}
return instance;
}
public void setInputController(net.java.games.input.Controller inputController) {
if (inputController != null) {
log.info(String.format("Using controller '%s'", inputController.getName()));
}
this.inputController = inputController;
onInputControllerChanged();
}
public net.java.games.input.Controller getInputController() {
return inputController;
}
public void setInputControllerIndex(int index) {
ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment();
net.java.games.input.Controller[] controllers = ce.getControllers();
if (controllers != null && index >= 0 && index < controllers.length) {
setInputController(controllers[index]);
}
}
public void loadKeyConfig() {
loadKeyConfig(Settings.getInstance().loadKeys());
}
public void loadKeyConfig(Map<Integer, keyCode> newLayout) {
keys.clear();
keys.putAll(newLayout);
}
public void loadControllerConfig() {
loadControllerConfig(Settings.getInstance().loadController());
}
public void loadControllerConfig(Map<keyCode, String> newLayout) {
controllerComponents.clear();
controllerComponents.putAll(newLayout);
onInputControllerChanged();
}
private void onInputControllerChanged() {
buttonComponents = new HashMap<Component.Identifier, Integer>();
for (Map.Entry<keyCode, String> entry : controllerComponents.entrySet()) {
keyCode key = entry.getKey();
String controllerName = entry.getValue();
Component component = getControllerComponentByName(controllerName);
if (component != null) {
Identifier identifier = component.getIdentifier();
boolean isAxis = identifier instanceof Axis;
if (isAxis && identifier == Axis.POV) {
povArrows = identifier;
} else {
int keyCode = -1;
switch (key) {
//
// PSP directional buttons can be mapped
// to a controller Axis or to a controller Button
//
case DOWN:
if (isAxis) {
digitalYAxis = identifier;
} else {
keyCode = PSP_CTRL_DOWN;
}
break;
case UP:
if (isAxis) {
digitalYAxis = identifier;
} else {
keyCode = PSP_CTRL_UP;
}
break;
case LEFT:
if (isAxis) {
digitalXAxis = identifier;
} else {
keyCode = PSP_CTRL_LEFT;
}
break;
case RIGHT:
if (isAxis) {
digitalXAxis = identifier;
} else {
keyCode = PSP_CTRL_RIGHT;
}
break;
//
// PSP analog controller can only be mapped to a controller Axis
//
case LANDOWN:
case LANUP:
if (isAxis) {
analogLYAxis = identifier;
}
break;
case LANLEFT:
case LANRIGHT:
if (isAxis) {
analogLXAxis = identifier;
}
break;
case RANDOWN:
case RANUP:
if (isAxis) {
analogRYAxis = identifier;
}
break;
case RANLEFT:
case RANRIGHT:
if (isAxis) {
analogRXAxis = identifier;
}
break;
//
// PSP buttons can be mapped either to a controller Button
// or to a controller Axis (e.g. a foot pedal)
//
case TRIANGLE:
keyCode = PSP_CTRL_TRIANGLE;
break;
case SQUARE:
keyCode = PSP_CTRL_SQUARE;
break;
case CIRCLE:
keyCode = PSP_CTRL_CIRCLE;
break;
case CROSS:
keyCode = PSP_CTRL_CROSS;
break;
case L1:
keyCode = PSP_CTRL_LTRIGGER;
break;
case R1:
keyCode = PSP_CTRL_RTRIGGER;
break;
case START:
keyCode = PSP_CTRL_START;
break;
case SELECT:
keyCode = PSP_CTRL_SELECT;
break;
case HOME:
keyCode = PSP_CTRL_HOME;
break;
case HOLD:
keyCode = PSP_CTRL_HOLD;
break;
case VOLMIN:
keyCode = PSP_CTRL_VOLDOWN;
break;
case VOLPLUS:
keyCode = PSP_CTRL_VOLUP;
break;
case SCREEN:
keyCode = PSP_CTRL_SCREEN;
break;
case MUSIC:
keyCode = PSP_CTRL_NOTE;
break;
case RELEASED:
break;
}
if (keyCode != -1) {
buttonComponents.put(component.getIdentifier(), keyCode);
}
}
}
}
}
/**
* Called by sceCtrl at every VBLANK interrupt.
*/
public void hleControllerPoll() {
processSpecialKeys();
pollController();
}
private void pollController() {
if (inputController != null && inputController.poll()) {
EventQueue eventQueue = inputController.getEventQueue();
Event event = new Event();
while (eventQueue.getNextEvent(event)) {
Component component = event.getComponent();
float value = event.getValue();
processControllerEvent(component, value);
}
}
}
public void keyPressed(KeyEvent keyEvent) {
keyPressed(keyEvent.getKeyCode());
}
public void keyPressed(int keyCode) {
keyCode key = keys.get(keyCode);
keyPressed(key);
}
public void keyPressed(keyCode key) {
if (key == null || key == lastKey) {
return;
}
switch (key) {
case DOWN:
Buttons |= PSP_CTRL_DOWN;
break;
case UP:
Buttons |= PSP_CTRL_UP;
break;
case LEFT:
Buttons |= PSP_CTRL_LEFT;
break;
case RIGHT:
Buttons |= PSP_CTRL_RIGHT;
break;
case LANDOWN:
Ly = (byte) 255;
break;
case LANUP:
Ly = 0;
break;
case LANLEFT:
Lx = 0;
break;
case LANRIGHT:
Lx = (byte) 255;
break;
case RANDOWN:
Ry = (byte) 255;
break;
case RANUP:
Ry = 0;
break;
case RANLEFT:
Rx = 0;
break;
case RANRIGHT:
Rx = (byte) 255;
break;
case TRIANGLE:
Buttons |= PSP_CTRL_TRIANGLE;
break;
case SQUARE:
Buttons |= PSP_CTRL_SQUARE;
break;
case CIRCLE:
Buttons |= PSP_CTRL_CIRCLE;
break;
case CROSS:
Buttons |= PSP_CTRL_CROSS;
break;
case L1:
Buttons |= PSP_CTRL_LTRIGGER;
break;
case R1:
Buttons |= PSP_CTRL_RTRIGGER;
break;
case START:
Buttons |= PSP_CTRL_START;
break;
case SELECT:
Buttons |= PSP_CTRL_SELECT;
break;
case HOME:
Buttons |= PSP_CTRL_HOME;
break;
case HOLD:
Buttons |= PSP_CTRL_HOLD;
break;
case VOLMIN:
Buttons |= PSP_CTRL_VOLDOWN;
break;
case VOLPLUS:
Buttons |= PSP_CTRL_VOLUP;
break;
case SCREEN:
Buttons |= PSP_CTRL_SCREEN;
break;
case MUSIC:
Buttons |= PSP_CTRL_NOTE;
break;
default:
return;
}
if (log.isDebugEnabled()) {
log.debug(String.format("keyPressed %s", key.toString()));
}
lastKey = key;
}
public void keyReleased(KeyEvent keyEvent) {
keyReleased(keyEvent.getKeyCode());
}
public void keyReleased(int keyCode) {
keyCode key = keys.get(keyCode);
keyReleased(key);
}
public void keyReleased(keyCode key) {
if (key == null) {
return;
}
switch (key) {
case DOWN:
Buttons &= ~PSP_CTRL_DOWN;
break;
case UP:
Buttons &= ~PSP_CTRL_UP;
break;
case LEFT:
Buttons &= ~PSP_CTRL_LEFT;
break;
case RIGHT:
Buttons &= ~PSP_CTRL_RIGHT;
break;
case LANDOWN:
Ly = analogCenter;
break;
case LANUP:
Ly = analogCenter;
break;
case LANLEFT:
Lx = analogCenter;
break;
case LANRIGHT:
Lx = analogCenter;
break;
case RANDOWN:
Ry = analogCenter;
break;
case RANUP:
Ry = analogCenter;
break;
case RANLEFT:
Rx = analogCenter;
break;
case RANRIGHT:
Rx = analogCenter;
break;
case TRIANGLE:
Buttons &= ~PSP_CTRL_TRIANGLE;
break;
case SQUARE:
Buttons &= ~PSP_CTRL_SQUARE;
break;
case CIRCLE:
Buttons &= ~PSP_CTRL_CIRCLE;
break;
case CROSS:
Buttons &= ~PSP_CTRL_CROSS;
break;
case L1:
Buttons &= ~PSP_CTRL_LTRIGGER;
break;
case R1:
Buttons &= ~PSP_CTRL_RTRIGGER;
break;
case START:
Buttons &= ~PSP_CTRL_START;
break;
case SELECT:
Buttons &= ~PSP_CTRL_SELECT;
break;
case HOME:
Buttons &= ~PSP_CTRL_HOME;
break;
case HOLD:
Buttons &= ~PSP_CTRL_HOLD;
break;
case VOLMIN:
Buttons &= ~PSP_CTRL_VOLDOWN;
break;
case VOLPLUS:
Buttons &= ~PSP_CTRL_VOLUP;
break;
case SCREEN:
Buttons &= ~PSP_CTRL_SCREEN;
break;
case MUSIC:
Buttons &= ~PSP_CTRL_NOTE;
break;
default:
return;
}
if (log.isDebugEnabled()) {
log.debug(String.format("keyReleased %s", key.toString()));
}
lastKey = Controller.keyCode.RELEASED;
}
private void processSpecialKeys() {
if (isSpecialKeyPressed(keyCode.VOLMIN)) {
Audio.setVolumeDown();
} else if (isSpecialKeyPressed(keyCode.VOLPLUS)) {
Audio.setVolumeUp();
} else if (isSpecialKeyPressed(keyCode.HOME)) {
Buttons &= ~PSP_CTRL_HOME; // Release the HOME button to avoid dialog spamming.
int opt = JOptionPane.showOptionDialog(null, "Exit the current application?", "HOME", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, null, null);
if (opt == JOptionPane.YES_OPTION) {
Modules.LoadExecForUserModule.triggerExitCallback();
}
}
}
// Check if a certain special key is pressed.
private boolean isSpecialKeyPressed(keyCode key) {
boolean res = false;
switch (key) {
case HOME:
if ((Buttons & PSP_CTRL_HOME) == PSP_CTRL_HOME) {
res = true;
}
break;
case HOLD:
if ((Buttons & PSP_CTRL_HOLD) == PSP_CTRL_HOLD) {
res = true;
}
break;
case VOLMIN:
if ((Buttons & PSP_CTRL_VOLDOWN) == PSP_CTRL_VOLDOWN) {
res = true;
}
break;
case VOLPLUS:
if ((Buttons & PSP_CTRL_VOLUP) == PSP_CTRL_VOLUP) {
res = true;
}
break;
case SCREEN:
if ((Buttons & PSP_CTRL_SCREEN) == PSP_CTRL_SCREEN) {
res = true;
}
break;
case MUSIC:
if ((Buttons & PSP_CTRL_NOTE) == PSP_CTRL_NOTE) {
res = true;
}
break;
default:
break;
}
return res;
}
private Component getControllerComponentByName(String name) {
Component[] components = inputController.getComponents();
if (components != null) {
// First search for the identifier name
for (int i = 0; i < components.length; i++) {
if (name.equals(components[i].getIdentifier().getName())) {
return components[i];
}
}
// Second search for the component name
for (int i = 0; i < components.length; i++) {
if (name.equals(components[i].getName())) {
return components[i];
}
}
}
return null;
}
public static float getDeadZone(Component component) {
float deadZone = component.getDeadZone();
if (deadZone < minimumDeadZone) {
deadZone = minimumDeadZone;
}
return deadZone;
}
public static boolean isInDeadZone(Component component, float value) {
return Math.abs(value) <= getDeadZone(component);
}
/**
* Convert a float value from the range [-1..1] to an analog byte value in
* the range [0..255]. -1 is converted to 0 0 is converted to 128 1 is
* converted to 255
*
* @param value value in the range [-1..1]
* @return the corresponding byte value in the range [0..255].
*/
private byte convertAnalogValue(float value) {
return (byte) ((value + 1f) * 127.5f);
}
private void processControllerEvent(Component component, float value) {
Component.Identifier id = component.getIdentifier();
if (log.isDebugEnabled()) {
log.debug(String.format("Controller Event on %s(%s): %f", component.getName(), id.getName(), value));
}
Integer button = buttonComponents.get(id);
if (button != null) {
if (id instanceof Axis) {
// An Axis has been mapped to a PSP button.
// E.g. for a foot pedal:
// value == 1.f when the pedal is not pressed
// value == 0.f when the pedal is halfway pressed
// value == -1.f when the pedal is pressed down
if (!isInDeadZone(component, value)) {
if (value >= 0.f) {
// Axis is pressed less than halfway, assume the PSP button is not pressed
Buttons &= ~button;
} else {
// Axis is pressed more than halfway, assume the PSP button is pressed
Buttons |= button;
}
}
} else {
if (value == 0.f) {
Buttons &= ~button;
} else if (value == 1.f) {
Buttons |= button;
} else {
log.warn(String.format("Unknown Controller Button Event on %s(%s): %f", component.getName(), id.getName(), value));
}
}
} else if (id == analogLXAxis) {
if (isInDeadZone(component, value)) {
Lx = analogCenter;
} else {
Lx = convertAnalogValue(value);
}
} else if (id == analogLYAxis) {
if (isInDeadZone(component, value)) {
Ly = analogCenter;
} else {
Ly = convertAnalogValue(value);
}
} else if (id == analogRXAxis) {
if (isInDeadZone(component, value)) {
Rx = analogCenter;
} else {
Rx = convertAnalogValue(value);
}
} else if (id == analogRYAxis) {
if (isInDeadZone(component, value)) {
Ry = analogCenter;
} else {
Ry = convertAnalogValue(value);
}
} else if (id == digitalXAxis) {
if (isInDeadZone(component, value)) {
Buttons &= ~(PSP_CTRL_LEFT | PSP_CTRL_RIGHT);
} else if (value < 0.f) {
Buttons |= PSP_CTRL_LEFT;
} else {
Buttons |= PSP_CTRL_RIGHT;
}
} else if (id == digitalYAxis) {
if (isInDeadZone(component, value)) {
Buttons &= ~(PSP_CTRL_DOWN | PSP_CTRL_UP);
} else if (value < 0.f) {
Buttons |= PSP_CTRL_UP;
} else {
Buttons |= PSP_CTRL_DOWN;
}
} else if (id == povArrows) {
if (value == POV.CENTER) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
} else if (value == POV.UP) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_UP;
} else if (value == POV.RIGHT) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_RIGHT;
} else if (value == POV.DOWN) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_DOWN;
} else if (value == POV.LEFT) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_LEFT;
} else if (value == POV.DOWN_LEFT) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_DOWN | PSP_CTRL_LEFT;
} else if (value == POV.DOWN_RIGHT) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_DOWN | PSP_CTRL_RIGHT;
} else if (value == POV.UP_LEFT) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_UP | PSP_CTRL_LEFT;
} else if (value == POV.UP_RIGHT) {
Buttons &= ~(PSP_CTRL_RIGHT | PSP_CTRL_LEFT | PSP_CTRL_DOWN | PSP_CTRL_UP);
Buttons |= PSP_CTRL_UP | PSP_CTRL_RIGHT;
} else {
log.warn(String.format("Unknown Controller Arrows Event on %s(%s): %f", component.getName(), id.getName(), value));
}
} else {
// Unknown Axis components are allowed to move inside their dead zone
// (e.g. due to small vibrations)
if (id instanceof Axis && (isInDeadZone(component, value) || id == Axis.Z || id == Axis.RZ)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unknown Controller Event in DeadZone on %s(%s): %f", component.getName(), id.getName(), value));
}
} else if (isKeyboardController(inputController)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unknown Keyboard Controller Event on %s(%s): %f", component.getName(), id.getName(), value));
}
} else {
if (log.isInfoEnabled()) {
log.warn(String.format("Unknown Controller Event on %s(%s): %f", component.getName(), id.getName(), value));
}
}
}
}
public byte getLx() {
return Lx;
}
public byte getLy() {
return Ly;
}
public byte getRx() {
return Rx;
}
public byte getRy() {
return Ry;
}
public int getButtons() {
return Buttons;
}
public boolean hasRightAnalogController() {
return hasRightAnalogController;
}
public void setHasRightAnalogController(boolean hasRightAnalogController) {
if (this.hasRightAnalogController != hasRightAnalogController) {
this.hasRightAnalogController = hasRightAnalogController;
init();
}
}
}