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