RemoteInput.java revision 0124e6dde9484f4cbc804ba0bd52ff308cf746b6
1package com.badlogic.gdx.input;
2
3import java.io.DataInputStream;
4import java.io.IOException;
5import java.net.InetAddress;
6import java.net.ServerSocket;
7import java.net.Socket;
8import java.util.HashSet;
9import java.util.Set;
10
11import com.badlogic.gdx.Gdx;
12import com.badlogic.gdx.Input;
13import com.badlogic.gdx.InputProcessor;
14import com.badlogic.gdx.utils.GdxRuntimeException;
15
16/**
17 * <p>An {@link Input} implementation that receives touch, key, accelerometer
18 * and compass events from a remote Android device. Just instantiate it
19 * and specify the port it should listen on for incoming connections (default
20 * 8190). Then store the new RemoteInput instance in Gdx.input. That's it.</p>
21 *
22 * <p>On your Android device you can use the gdx-remote application available
23 * on the Google Code page as an APK or in SVN (extensions/gdx-remote). Open it,
24 * specify the IP address and the port of the PC your libgdx app is running on
25 * and then tap away.</p>
26 *
27 * <p>The touch coordinates will be translated to the desktop window's coordinate
28 * system, no matter the orientation of the device</p>
29 *
30 * @author mzechner
31 *
32 */
33public class RemoteInput implements Runnable, Input {
34	class KeyEvent {
35		static final int KEY_DOWN = 0;
36		static final int KEY_UP = 1;
37		static final int KEY_TYPED = 2;
38
39		long timeStamp;
40		int type;
41		int keyCode;
42		char keyChar;
43	}
44
45	class TouchEvent {
46		static final int TOUCH_DOWN = 0;
47		static final int TOUCH_UP = 1;
48		static final int TOUCH_DRAGGED = 2;
49
50		long timeStamp;
51		int type;
52		int x;
53		int y;
54		int pointer;
55	}
56
57	class EventTrigger implements Runnable {
58		TouchEvent touchEvent;
59		KeyEvent keyEvent;
60
61		public EventTrigger(TouchEvent touchEvent, KeyEvent keyEvent) {
62			this.touchEvent = touchEvent;
63			this.keyEvent = keyEvent;
64		}
65
66		@Override public void run () {
67			justTouched = false;
68			if(processor != null) {
69				if(touchEvent != null) {
70					touchX[touchEvent.pointer] = touchEvent.x;
71					touchY[touchEvent.pointer] = touchEvent.y;
72					switch(touchEvent.type) {
73					case TouchEvent.TOUCH_DOWN:
74						processor.touchDown(touchEvent.x, touchEvent.y, touchEvent.pointer, Input.Buttons.LEFT);
75						isTouched[touchEvent.pointer] = true;
76						justTouched = true;
77						break;
78					case TouchEvent.TOUCH_UP:
79						processor.touchUp(touchEvent.x, touchEvent.y, touchEvent.pointer, Input.Buttons.LEFT);
80						isTouched[touchEvent.pointer] = false;
81						break;
82					case TouchEvent.TOUCH_DRAGGED:
83						processor.touchDragged(touchEvent.x, touchEvent.y, touchEvent.pointer);
84						break;
85					}
86				}
87				if(keyEvent != null) {
88					switch(keyEvent.type) {
89					case KeyEvent.KEY_DOWN:
90						processor.keyDown(keyEvent.keyCode);
91						keys.add(keyEvent.keyCode);
92						break;
93					case KeyEvent.KEY_UP:
94						processor.keyUp(keyEvent.keyCode);
95						keys.remove(keyEvent.keyCode);
96						break;
97					case KeyEvent.KEY_TYPED:
98						processor.keyTyped(keyEvent.keyChar);
99						break;
100					}
101				}
102			} else {
103				if(touchEvent != null) {
104					touchX[touchEvent.pointer] = touchEvent.x;
105					touchY[touchEvent.pointer] = touchEvent.y;
106					if(touchEvent.type == TouchEvent.TOUCH_DOWN) {
107						isTouched[touchEvent.pointer] = true;
108						justTouched = true;
109					}
110					if(touchEvent.type == TouchEvent.TOUCH_UP) {
111						isTouched[touchEvent.pointer] = false;
112					}
113				}
114				if(keyEvent != null) {
115					if(keyEvent.type == KeyEvent.KEY_DOWN) keys.add(keyEvent.keyCode);
116					if(keyEvent.type == KeyEvent.KEY_UP) keys.remove(keyEvent.keyCode);
117				}
118			}
119		}
120	}
121
122	public static int DEFAULT_PORT = 8190;
123	private ServerSocket serverSocket;
124	private float[] accel = new float[3];
125	private float[] compass = new float[3];
126	private boolean multiTouch = false;
127	private float remoteWidth = 0;
128	private float remoteHeight = 0;
129	private Set<Integer> keys = new HashSet<Integer>();
130	private int[] touchX = new int[20];
131	private int[] touchY = new int[20];
132	private boolean isTouched[] = new boolean[20];
133	private boolean justTouched = false;
134	private InputProcessor processor = null;
135	private final int port;
136	public final String[] ips;
137
138	public RemoteInput() {
139		this(DEFAULT_PORT);
140	}
141
142	public RemoteInput(int port) {
143		try {
144			this.port = port;
145			serverSocket = new ServerSocket(port);
146			Thread thread = new Thread(this);
147			thread.setDaemon(true);
148			thread.start();
149			InetAddress[] allByName = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
150			ips = new String[allByName.length];
151			for(int i = 0; i < allByName.length; i++) {
152				ips[i] = allByName[i].getHostAddress();
153			}
154		} catch(Exception e) {
155			throw new GdxRuntimeException("Couldn't open listening socket at port '" + port+ "'", e);
156		}
157	}
158
159	@Override public void run () {
160		while(true) {
161			try {
162				System.out.println("listening, port " + port);
163				Socket socket = null;
164				while(true) {
165						socket = serverSocket.accept();
166						break;
167				}
168				socket.setTcpNoDelay(true);
169				socket.setSoTimeout(3000);
170
171				DataInputStream in = new DataInputStream(socket.getInputStream());
172				multiTouch = in.readBoolean();
173				while(true) {
174					int event = in.readInt();
175					KeyEvent keyEvent = null;
176					TouchEvent touchEvent = null;
177					switch(event) {
178					case RemoteSender.ACCEL:
179						accel[0] = in.readFloat();
180						accel[1] = in.readFloat();
181						accel[2] = in.readFloat();
182						break;
183					case RemoteSender.COMPASS:
184						compass[0] = in.readFloat();
185						compass[1] = in.readFloat();
186						compass[2] = in.readFloat();
187						break;
188					case RemoteSender.SIZE:
189						remoteWidth = in.readFloat();
190						remoteHeight = in.readFloat();
191						break;
192					case RemoteSender.KEY_DOWN:
193						keyEvent = new KeyEvent();
194						keyEvent.keyCode = in.readInt();
195						keyEvent.type = KeyEvent.KEY_DOWN;
196						break;
197					case RemoteSender.KEY_UP:
198						keyEvent = new KeyEvent();
199						keyEvent.keyCode = in.readInt();
200						keyEvent.type = KeyEvent.KEY_UP;
201						break;
202					case RemoteSender.KEY_TYPED:
203						keyEvent = new KeyEvent();
204						keyEvent.keyChar = in.readChar();
205						keyEvent.type = KeyEvent.KEY_TYPED;
206						break;
207					case RemoteSender.TOUCH_DOWN:
208						touchEvent = new TouchEvent();
209						touchEvent.x = (int)((in.readInt() / remoteWidth) * Gdx.graphics.getWidth());
210						touchEvent.y = (int)((in.readInt() / remoteHeight) * Gdx.graphics.getHeight());
211						touchEvent.pointer = in.readInt();
212						touchEvent.type = TouchEvent.TOUCH_DOWN;
213						break;
214					case RemoteSender.TOUCH_UP:
215						touchEvent = new TouchEvent();
216						touchEvent.x = (int)((in.readInt() / remoteWidth) * Gdx.graphics.getWidth());
217						touchEvent.y = (int)((in.readInt() / remoteHeight) * Gdx.graphics.getHeight());
218						touchEvent.pointer = in.readInt();
219						touchEvent.type = TouchEvent.TOUCH_UP;
220						break;
221					case RemoteSender.TOUCH_DRAGGED:
222						touchEvent = new TouchEvent();
223						touchEvent.x = (int)((in.readInt() / remoteWidth) * Gdx.graphics.getWidth());
224						touchEvent.y = (int)((in.readInt() / remoteHeight) * Gdx.graphics.getHeight());
225						touchEvent.pointer = in.readInt();
226						touchEvent.type = TouchEvent.TOUCH_DRAGGED;
227						break;
228					}
229
230					Gdx.app.postRunnable(new EventTrigger(touchEvent, keyEvent));
231				}
232			} catch (IOException e) {
233				e.printStackTrace();
234			}
235		}
236	}
237
238	@Override public boolean isAccelerometerAvailable () {
239		return true;
240	}
241
242	@Override public float getAccelerometerX () {
243		return accel[0];
244	}
245
246	@Override public float getAccelerometerY () {
247		return accel[1];
248	}
249
250	@Override public float getAccelerometerZ () {
251		return accel[2];
252	}
253
254	@Override public int getX () {
255		return touchX[0];
256	}
257
258	@Override public int getX (int pointer) {
259		return touchX[pointer];
260	}
261
262	@Override public int getY () {
263		return touchY[0];
264	}
265
266	@Override public int getY (int pointer) {
267		return touchY[pointer];
268	}
269
270	@Override public boolean isTouched () {
271		return isTouched[0];
272	}
273
274	@Override public boolean justTouched () {
275		return justTouched;
276	}
277
278	@Override public boolean isTouched (int pointer) {
279		return isTouched[pointer];
280	}
281
282	@Override public boolean isButtonPressed (int button) {
283		if(button != Buttons.LEFT) return false;
284		for(int i = 0; i < isTouched.length; i++)
285			if(isTouched[i]) return true;
286		return false;
287	}
288
289	@Override public boolean isKeyPressed (int key) {
290		return keys.contains(key);
291	}
292
293	@Override public void getTextInput (TextInputListener listener, String title, String text) {
294		Gdx.app.getInput().getTextInput(listener, title, text);
295	}
296
297	@Override public void setOnscreenKeyboardVisible (boolean visible) {
298	}
299
300	@Override public boolean supportsOnscreenKeyboard () {
301		return false;
302	}
303
304	@Override public boolean supportsMultitouch () {
305		return multiTouch;
306	}
307
308	@Override public boolean supportsVibrator () {
309		return true;
310	}
311
312	@Override public void vibrate (int milliseconds) {
313
314	}
315
316	@Override public void vibrate (long[] pattern, int repeat) {
317
318	}
319
320	@Override public void cancelVibrate () {
321
322	}
323
324	@Override public boolean supportsCompass () {
325		return true;
326	}
327
328	@Override public float getAzimuth () {
329		return compass[0];
330	}
331
332	@Override public float getPitch () {
333		return compass[1];
334	}
335
336	@Override public float getRoll () {
337		return compass[2];
338	}
339
340	@Override public void setCatchBackKey (boolean catchBack) {
341
342	}
343
344	@Override public void setInputProcessor (InputProcessor processor) {
345		this.processor = processor;
346	}
347
348	/**
349	 * @return the IP addresses {@link RemoteSender} or gdx-remote should connect to. Most likely the LAN addresses if behind a NAT.
350	 */
351	public String[] getIPs() {
352		return ips;
353	}
354}
355