package com.badlogic.gdx.automation.recorder; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.automation.recorder.InputProperty.SyncProperty; import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.IntMap.Keys; /** * Helper class to get the sets of pressed keys from the different input types. * Uses reflection to retrieve the collections of pressed keys. Currently only * desktop and android supported. * * @author Lukas Böhm * */ class EventBufferAccessHelper { static final ArrayList<KeyEvent> keyEvents = new ArrayList<KeyEvent>(); static final ArrayList<PointerEvent> pointerEvents = new ArrayList<PointerEvent>(); // TODO SparseArray is shit. Replace with 256 byte ByteBuffer like in lwjgl private static final SparseArray<Boolean> pressedKeys = new SparseArray<Boolean>(); private static List<Object> inputKeyEvents = null; private static List<Object> inputPointerEvents = null; private static IntMap<Object> keysPressedAndroid = null; private static java.nio.ByteBuffer keysPressedDesktop = null; private static Input keyPressedFrom = null; private static Input pointerEventsFrom = null; private static Input keyEventsFrom = null; static class KeyEvent { public KeyEvent() { } public KeyEvent(SyncProperty.KeyEvent event) { keyChar = event.keyChar; keyCode = event.keyCode; type = event.type; } long timeStamp; SyncProperty.KeyEvent.Type type; int keyCode; char keyChar; } static class PointerEvent { public PointerEvent() { } public PointerEvent(SyncProperty.PointerEvent event) { button = event.button; pointer = event.pointer; scrollAmount = event.scrollAmount; type = event.type; x = (int) event.x; y = (int) event.y; } long timeStamp; SyncProperty.PointerEvent.Type type; int x; int y; int scrollAmount; int button; int pointer; } private EventBufferAccessHelper() { } public static List<KeyEvent> accessKeyEvents(Input input) { return accessKeyEvents(input, true); } /** * * @param input * @param update * if LWJGL events should be pulled before accessing the events. * This is necessary if you do not access the events between a * call to {@link LwjglInput#update update} and the end of * {@link LwjglInput#processEvents processEvents} * @return */ @SuppressWarnings("unchecked") static List<KeyEvent> accessKeyEvents(Input input, boolean update) { while (input instanceof InputProxy) { input = ((InputProxy) input).getProxiedInput(); } if (update && Gdx.app.getType() == ApplicationType.Desktop) { callMethod("updateKeyboard", input, null, null); } if (inputKeyEvents == null || keyEventsFrom != input) { inputKeyEvents = (List<Object>) accessField( getField(input.getClass(), "keyEvents"), input); keyEventsFrom = input; } synchronized (keyEvents) { keyEvents.clear(); for (Object event : inputKeyEvents) { KeyEvent e = new KeyEvent(); e.keyChar = (Character) accessField( getField(event.getClass(), "keyChar"), event); e.keyCode = (Integer) accessField( getField(event.getClass(), "keyCode"), event); e.timeStamp = (Long) accessField( getField(event.getClass(), "timeStamp"), event); if (Gdx.app.getType() == ApplicationType.Android) { e.type = SyncProperty.KeyEvent.Type .mapAndroid((Integer) accessField( getField(event.getClass(), "type"), event)); } else if (Gdx.app.getType() == ApplicationType.Desktop) { e.type = SyncProperty.KeyEvent.Type .mapDesktop((Integer) accessField( getField(event.getClass(), "type"), event)); } else { throw new IllegalStateException( "Recorder is not supporting backend " + Gdx.app.getType()); } keyEvents.add(e); } } return keyEvents; } public static List<PointerEvent> accessPointerEvents(Input input) { return accessPointerEvents(input, true); } /** * * @param input * @param update * if LWJGL events should be pulled before accessing the events. * This is necessary if you do not access the events between a * call to {@link LwjglInput#update update} and the end of * {@link LwjglInput#processEvents processEvents} * @return */ @SuppressWarnings("unchecked") static List<PointerEvent> accessPointerEvents(Input input, boolean update) { while (input instanceof InputProxy) { input = ((InputProxy) input).getProxiedInput(); } if (update && Gdx.app.getType() == ApplicationType.Desktop) { callMethod("updateMouse", input, null, null); } if (inputPointerEvents == null || pointerEventsFrom != input) { inputPointerEvents = (List<Object>) accessField( getField(input.getClass(), "touchEvents"), input); pointerEventsFrom = input; } synchronized (pointerEvents) { pointerEvents.clear(); for (Object event : inputPointerEvents) { PointerEvent e = new PointerEvent(); e.x = (Integer) accessField(getField(event.getClass(), "x"), event); e.y = (Integer) accessField(getField(event.getClass(), "y"), event); e.timeStamp = (Long) accessField( getField(event.getClass(), "timeStamp"), event); e.pointer = (Integer) accessField( getField(event.getClass(), "pointer"), event); if (Gdx.app.getType() == ApplicationType.Desktop) { e.scrollAmount = (Integer) accessField( getField(event.getClass(), "scrollAmount"), event); e.button = (Integer) accessField( getField(event.getClass(), "button"), event); e.type = SyncProperty.PointerEvent.Type .mapDesktop((Integer) accessField( getField(event.getClass(), "type"), event)); } else { e.type = SyncProperty.PointerEvent.Type .mapAndroid((Integer) accessField( getField(event.getClass(), "type"), event)); } pointerEvents.add(e); } } return pointerEvents; } public static void copyKeyEvents(Input input, List<KeyEvent> copyInto, boolean update) { copyInto.clear(); synchronized (keyEvents) { copyInto.addAll(accessKeyEvents(input, update)); } } public static void copyPointerEvents(Input input, List<PointerEvent> copyInto, boolean update) { copyInto.clear(); synchronized (pointerEvents) { copyInto.addAll(accessPointerEvents(input, update)); } } public static SparseArray<Boolean> accessPressedKeys(Input input) { synchronized (pressedKeys) { pressedKeys.clear(); Object synchronizer = getKeySynchronizer(input); synchronized (synchronizer) { copyPressedKeys(input); } } return pressedKeys; } @SuppressWarnings("unchecked") private static void copyPressedKeys(Input input) { // TODO no actual input type check. Just assuming on app type while (input instanceof InputProxy) { input = ((InputProxy) input).getProxiedInput(); } if (Gdx.app.getType() == ApplicationType.Android) { if (keysPressedAndroid == null || keyPressedFrom != input) { keysPressedAndroid = (IntMap<Object>) accessField( getField(input.getClass(), "keys"), input); keyPressedFrom = input; } Keys keysKeys = keysPressedAndroid.keys(); while (keysKeys.hasNext) { pressedKeys.append(keysKeys.next(), Boolean.TRUE); } } else if (Gdx.app.getType() == ApplicationType.Desktop) { if (keysPressedDesktop == null || keyPressedFrom != input) { keysPressedDesktop = (ByteBuffer) accessField( getField(getClass("org.lwjgl.input.Keyboard"), "keyDownBuffer"), null); keyPressedFrom = input; } for (int i = 0; i < keysPressedDesktop.capacity(); i++) { if (keysPressedDesktop.get(i) != 0) { pressedKeys.put(i, Boolean.TRUE); } } } else { throw new IllegalStateException("Unsupported application type: " + Gdx.app.getType()); } } private static Object getKeySynchronizer(Input input) { while (input instanceof InputProxy) { input = ((InputProxy) input).getProxiedInput(); } Object synchronizer = null; if (Gdx.app.getType() == ApplicationType.Android) { // Android: The AndroidInput itself if (getClass("com.badlogic.gdx.backends.android.AndroidInput") .isAssignableFrom(input.getClass())) { synchronizer = input; } else { throw new IllegalStateException( "Unsupported or unexpected input type: " + Gdx.input.getClass().getName()); } } else if (Gdx.app.getType() == ApplicationType.Desktop) { // LWJGL: OpenGLPackageAccess.global_lock Class<?> openGLPackageAccess = getClass("org.lwjgl.input.OpenGLPackageAccess"); Field global_lock = getField(openGLPackageAccess, "global_lock"); synchronizer = accessField(global_lock, null); } else { throw new IllegalStateException("Unsupported application type: " + Gdx.app.getType()); } return synchronizer; } public static void copyPressedKeys(Input input, SparseArray<Boolean> copyInto) { copyInto.clear(); synchronized (pressedKeys) { copyInto.putAll(accessPressedKeys(input)); } } /* * Reflection helper methods */ private static Class<?> getClass(String name) { Class<?> result; try { result = Class.forName(name); } catch (ClassNotFoundException e) { throw new IllegalStateException("Coluld not get class: " + name); } return result; } private static Field getField(Class<?> clazz, String name) { Field field; try { field = clazz.getDeclaredField(name); } catch (NoSuchFieldException e) { throw new IllegalStateException("Field does not exist: " + name + " in class " + clazz.getName()); } catch (SecurityException e) { throw e; } return field; } private static Object accessField(Field f, Object instance) { Object field; try { f.setAccessible(true); field = f.get(instance); } catch (IllegalArgumentException e) { throw e; } catch (IllegalAccessException e) { throw new IllegalStateException("IllegalAccess: " + e.getLocalizedMessage()); } return field; } private static Object callMethod(String name, Object o, Class<?>[] parameterTypes, Object[] args) { try { Method method = o.getClass() .getDeclaredMethod(name, parameterTypes); method.setAccessible(true); return method.invoke(o, args); } catch (NoSuchMethodException e) { throw new IllegalStateException("No such method: " + name + " in " + o.getClass()); } catch (SecurityException e) { throw e; } catch (IllegalAccessException e) { throw new IllegalStateException( "Method enforces Java access control: " + name + " in " + o.getClass()); } catch (IllegalArgumentException e) { throw e; } catch (InvocationTargetException e) { throw new IllegalStateException("Called method " + name + " in " + o.getClass() + " threw exception"); } } }