1 /*************************************************************************/
2 /*  GodotInputHandler.java                                               */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 
31 package org.godotengine.godot.input;
32 
33 import static org.godotengine.godot.utils.GLUtils.DEBUG;
34 
35 import org.godotengine.godot.GodotLib;
36 import org.godotengine.godot.GodotView;
37 import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
38 
39 import android.util.Log;
40 import android.view.InputDevice;
41 import android.view.InputDevice.MotionRange;
42 import android.view.KeyEvent;
43 import android.view.MotionEvent;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.Comparator;
48 import java.util.List;
49 
50 /**
51  * Handles input related events for the {@link GodotView} view.
52  */
53 public class GodotInputHandler implements InputDeviceListener {
54 
55 	private final ArrayList<Joystick> joysticksDevices = new ArrayList<Joystick>();
56 
57 	private final GodotView godotView;
58 	private final InputManagerCompat inputManager;
59 
GodotInputHandler(GodotView godotView)60 	public GodotInputHandler(GodotView godotView) {
61 		this.godotView = godotView;
62 		this.inputManager = InputManagerCompat.Factory.getInputManager(godotView.getContext());
63 		this.inputManager.registerInputDeviceListener(this, null);
64 	}
65 
queueEvent(Runnable task)66 	private void queueEvent(Runnable task) {
67 		godotView.queueEvent(task);
68 	}
69 
isKeyEvent_GameDevice(int source)70 	private boolean isKeyEvent_GameDevice(int source) {
71 		// Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD)
72 		if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD))
73 			return false;
74 
75 		return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
76 	}
77 
onKeyUp(final int keyCode, KeyEvent event)78 	public boolean onKeyUp(final int keyCode, KeyEvent event) {
79 		if (keyCode == KeyEvent.KEYCODE_BACK) {
80 			return true;
81 		}
82 
83 		if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
84 			return false;
85 		};
86 
87 		int source = event.getSource();
88 		if (isKeyEvent_GameDevice(source)) {
89 
90 			final int button = getGodotButton(keyCode);
91 			final int device_id = findJoystickDevice(event.getDeviceId());
92 
93 			// Check if the device exists
94 			if (device_id > -1) {
95 				queueEvent(new Runnable() {
96 					@Override
97 					public void run() {
98 						GodotLib.joybutton(device_id, button, false);
99 					}
100 				});
101 			}
102 		} else {
103 			final int chr = event.getUnicodeChar(0);
104 			queueEvent(new Runnable() {
105 				@Override
106 				public void run() {
107 					GodotLib.key(keyCode, chr, false);
108 				}
109 			});
110 		};
111 
112 		return true;
113 	}
114 
onKeyDown(final int keyCode, KeyEvent event)115 	public boolean onKeyDown(final int keyCode, KeyEvent event) {
116 		if (keyCode == KeyEvent.KEYCODE_BACK) {
117 			godotView.onBackPressed();
118 			// press 'back' button should not terminate program
119 			//normal handle 'back' event in game logic
120 			return true;
121 		}
122 
123 		if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
124 			return false;
125 		};
126 
127 		int source = event.getSource();
128 		//Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
129 
130 		if (isKeyEvent_GameDevice(source)) {
131 
132 			if (event.getRepeatCount() > 0) // ignore key echo
133 				return true;
134 
135 			final int button = getGodotButton(keyCode);
136 			final int device_id = findJoystickDevice(event.getDeviceId());
137 
138 			// Check if the device exists
139 			if (device_id > -1) {
140 				queueEvent(new Runnable() {
141 					@Override
142 					public void run() {
143 						GodotLib.joybutton(device_id, button, true);
144 					}
145 				});
146 			}
147 		} else {
148 			final int chr = event.getUnicodeChar(0);
149 			queueEvent(new Runnable() {
150 				@Override
151 				public void run() {
152 					GodotLib.key(keyCode, chr, true);
153 				}
154 			});
155 		};
156 
157 		return true;
158 	}
159 
onGenericMotionEvent(MotionEvent event)160 	public boolean onGenericMotionEvent(MotionEvent event) {
161 		if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
162 
163 			final int device_id = findJoystickDevice(event.getDeviceId());
164 
165 			// Check if the device exists
166 			if (device_id > -1) {
167 				Joystick joy = joysticksDevices.get(device_id);
168 
169 				for (int i = 0; i < joy.axes.size(); i++) {
170 					InputDevice.MotionRange range = joy.axes.get(i);
171 					final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
172 					final int idx = i;
173 					queueEvent(new Runnable() {
174 						@Override
175 						public void run() {
176 							GodotLib.joyaxis(device_id, idx, value);
177 						}
178 					});
179 				}
180 
181 				for (int i = 0; i < joy.hats.size(); i += 2) {
182 					final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
183 					final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
184 					queueEvent(new Runnable() {
185 						@Override
186 						public void run() {
187 							GodotLib.joyhat(device_id, hatX, hatY);
188 						}
189 					});
190 				}
191 				return true;
192 			}
193 		} else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) {
194 			final int x = Math.round(event.getX());
195 			final int y = Math.round(event.getY());
196 			final int type = event.getAction();
197 			queueEvent(new Runnable() {
198 				@Override
199 				public void run() {
200 					GodotLib.hover(type, x, y);
201 				}
202 			});
203 			return true;
204 		}
205 
206 		return false;
207 	}
208 
initInputDevices()209 	public void initInputDevices() {
210 		/* initially add input devices*/
211 		int[] deviceIds = inputManager.getInputDeviceIds();
212 		for (int deviceId : deviceIds) {
213 			InputDevice device = inputManager.getInputDevice(deviceId);
214 			if (DEBUG) {
215 				Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
216 			}
217 			onInputDeviceAdded(deviceId);
218 		}
219 	}
220 
221 	@Override
onInputDeviceAdded(int deviceId)222 	public void onInputDeviceAdded(int deviceId) {
223 		int id = findJoystickDevice(deviceId);
224 
225 		// Check if the device has not been already added
226 		if (id < 0) {
227 			InputDevice device = inputManager.getInputDevice(deviceId);
228 			//device can be null if deviceId is not found
229 			if (device != null) {
230 				int sources = device.getSources();
231 				if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
232 						((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
233 					id = joysticksDevices.size();
234 
235 					Joystick joy = new Joystick();
236 					joy.device_id = deviceId;
237 					joy.name = device.getName();
238 					joy.axes = new ArrayList<InputDevice.MotionRange>();
239 					joy.hats = new ArrayList<InputDevice.MotionRange>();
240 
241 					List<InputDevice.MotionRange> ranges = device.getMotionRanges();
242 					Collections.sort(ranges, new RangeComparator());
243 
244 					for (InputDevice.MotionRange range : ranges) {
245 						if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
246 							joy.hats.add(range);
247 						} else {
248 							joy.axes.add(range);
249 						}
250 					}
251 
252 					joysticksDevices.add(joy);
253 
254 					final int device_id = id;
255 					final String name = joy.name;
256 					queueEvent(new Runnable() {
257 						@Override
258 						public void run() {
259 							GodotLib.joyconnectionchanged(device_id, true, name);
260 						}
261 					});
262 				}
263 			}
264 		}
265 	}
266 
267 	@Override
onInputDeviceRemoved(int deviceId)268 	public void onInputDeviceRemoved(int deviceId) {
269 		final int device_id = findJoystickDevice(deviceId);
270 
271 		// Check if the evice has not been already removed
272 		if (device_id > -1) {
273 			joysticksDevices.remove(device_id);
274 
275 			queueEvent(new Runnable() {
276 				@Override
277 				public void run() {
278 					GodotLib.joyconnectionchanged(device_id, false, "");
279 				}
280 			});
281 		}
282 	}
283 
284 	@Override
onInputDeviceChanged(int deviceId)285 	public void onInputDeviceChanged(int deviceId) {
286 		onInputDeviceRemoved(deviceId);
287 		onInputDeviceAdded(deviceId);
288 	}
289 
290 	private static class RangeComparator implements Comparator<MotionRange> {
291 		@Override
compare(MotionRange arg0, MotionRange arg1)292 		public int compare(MotionRange arg0, MotionRange arg1) {
293 			return arg0.getAxis() - arg1.getAxis();
294 		}
295 	}
296 
getGodotButton(int keyCode)297 	public static int getGodotButton(int keyCode) {
298 		int button;
299 		switch (keyCode) {
300 			case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B
301 				button = 0;
302 				break;
303 			case KeyEvent.KEYCODE_BUTTON_B:
304 				button = 1;
305 				break;
306 			case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y
307 				button = 2;
308 				break;
309 			case KeyEvent.KEYCODE_BUTTON_Y:
310 				button = 3;
311 				break;
312 			case KeyEvent.KEYCODE_BUTTON_L1:
313 				button = 9;
314 				break;
315 			case KeyEvent.KEYCODE_BUTTON_L2:
316 				button = 15;
317 				break;
318 			case KeyEvent.KEYCODE_BUTTON_R1:
319 				button = 10;
320 				break;
321 			case KeyEvent.KEYCODE_BUTTON_R2:
322 				button = 16;
323 				break;
324 			case KeyEvent.KEYCODE_BUTTON_SELECT:
325 				button = 4;
326 				break;
327 			case KeyEvent.KEYCODE_BUTTON_START:
328 				button = 6;
329 				break;
330 			case KeyEvent.KEYCODE_BUTTON_THUMBL:
331 				button = 7;
332 				break;
333 			case KeyEvent.KEYCODE_BUTTON_THUMBR:
334 				button = 8;
335 				break;
336 			case KeyEvent.KEYCODE_DPAD_UP:
337 				button = 11;
338 				break;
339 			case KeyEvent.KEYCODE_DPAD_DOWN:
340 				button = 12;
341 				break;
342 			case KeyEvent.KEYCODE_DPAD_LEFT:
343 				button = 13;
344 				break;
345 			case KeyEvent.KEYCODE_DPAD_RIGHT:
346 				button = 14;
347 				break;
348 			case KeyEvent.KEYCODE_BUTTON_C:
349 				button = 17;
350 				break;
351 			case KeyEvent.KEYCODE_BUTTON_Z:
352 				button = 18;
353 				break;
354 
355 			default:
356 				button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20;
357 				break;
358 		}
359 		return button;
360 	}
361 
findJoystickDevice(int device_id)362 	private int findJoystickDevice(int device_id) {
363 		for (int i = 0; i < joysticksDevices.size(); i++) {
364 			if (joysticksDevices.get(i).device_id == device_id) {
365 				return i;
366 			}
367 		}
368 
369 		return -1;
370 	}
371 }
372