diff --git a/README.md b/README.md index 0b4c995..2d1d21a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ It consists of two components: * the input driver for your PC The GfxTablet app sends motion and touch events via UDP to a specified host -on port 40117. +on port 40118. The input driver must be installed on your PC. It creates a virtual "network tablet" on your PC that is controlled by your Android device. diff --git a/app-android/AndroidManifest.xml b/app-android/AndroidManifest.xml index c19dd71..9ff57d7 100644 --- a/app-android/AndroidManifest.xml +++ b/app-android/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="2" + android:versionName="1.1" > { @Override protected Boolean doInBackground(Void... params) { @@ -103,7 +124,8 @@ public class CanvasView extends View implements OnSharedPreferenceChangeListener setEnabled(true); else { setEnabled(false); - Toast.makeText(getContext(), "Unknown host name, network tablet disabled!", Toast.LENGTH_LONG).show(); + Toast.makeText(getContext(), "Unknown host name, please configure", Toast.LENGTH_LONG).show(); + getContext().startActivity(new Intent(getContext(), SettingsActivity.class)); } } } diff --git a/app-android/src/at/bitfire/gfxtablet/NetButtonEvent.java b/app-android/src/at/bitfire/gfxtablet/NetButtonEvent.java deleted file mode 100644 index e1183c0..0000000 --- a/app-android/src/at/bitfire/gfxtablet/NetButtonEvent.java +++ /dev/null @@ -1,34 +0,0 @@ -package at.bitfire.gfxtablet; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class NetButtonEvent extends NetEvent { - boolean down; - - public NetButtonEvent(int x, int y, int pressure, boolean down) { - super(x, y, pressure); - this.down = down; - } - - @Override - public byte[] toByteArray() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(baos); - - try { - dos.write(1); /* EVENT_TYPE_BUTTON */ - dos.writeShort(x); - dos.writeShort(y); - dos.writeShort(pressure); - dos.write(1); - dos.write(down ? 1 : 0); - } catch (IOException e) { - return null; - } - - return baos.toByteArray(); - } - -} diff --git a/app-android/src/at/bitfire/gfxtablet/NetConfigurationEvent.java b/app-android/src/at/bitfire/gfxtablet/NetConfigurationEvent.java deleted file mode 100644 index af52e04..0000000 --- a/app-android/src/at/bitfire/gfxtablet/NetConfigurationEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package at.bitfire.gfxtablet; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class NetConfigurationEvent extends NetEvent { - public NetConfigurationEvent(int x, int y, int pressure) { - super(x, y, pressure); - } - - @Override - public byte[] toByteArray() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(baos); - - try { - dos.write(2); /* EVENT_TYPE_SET_RESOLUTION */ - dos.writeShort(x); - dos.writeShort(y); - dos.writeShort(pressure); - } catch (IOException e) { - return null; - } - - return baos.toByteArray(); - } -} diff --git a/app-android/src/at/bitfire/gfxtablet/NetDisconnectEvent.java b/app-android/src/at/bitfire/gfxtablet/NetDisconnectEvent.java deleted file mode 100644 index 23fd1b3..0000000 --- a/app-android/src/at/bitfire/gfxtablet/NetDisconnectEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package at.bitfire.gfxtablet; - -public class NetDisconnectEvent extends NetEvent { - public NetDisconnectEvent() { - super(0, 0, 0); - } - - @Override - public byte[] toByteArray() { - return null; - } -} diff --git a/app-android/src/at/bitfire/gfxtablet/NetEvent.java b/app-android/src/at/bitfire/gfxtablet/NetEvent.java index fdb543b..7940396 100644 --- a/app-android/src/at/bitfire/gfxtablet/NetEvent.java +++ b/app-android/src/at/bitfire/gfxtablet/NetEvent.java @@ -1,17 +1,77 @@ package at.bitfire.gfxtablet; -public abstract class NetEvent { - int x, y, pressure; - - public int getX() { return x; } - public int getY() { return y; } - public int getPressure() { return pressure; } +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; - public NetEvent(int x, int y, int pressure) { - this.x = Math.max(x, 0); - this.y = Math.max(y, 0); +public class NetEvent { + enum Type { + TYPE_MOTION, + TYPE_BUTTON, + + // not specified in protocol, only needed to shut down network thread + TYPE_DISCONNECT + }; + static final String signature = "GfxTablet"; + static final short protocol_version = 1; + + Type type; + short x, y, pressure; + byte button, button_down; + + public short getX() { return x; } + public short getY() { return y; } + public short getPressure() { return pressure; } + + + public NetEvent(Type type) { + this.type = type; + } + + public NetEvent(Type type, short x, short y, short pressure) { + this.type = type; + this.x = x; + this.y = y; this.pressure = pressure; } + + public NetEvent(Type type, short x, short y, short pressure, int button, boolean button_down) { + this(type, x, y, pressure); + this.button = (byte)button; + this.button_down = (byte)(button_down ? 1 : 0); + } - public abstract byte[] toByteArray(); + public byte[] toByteArray() { + if (type == Type.TYPE_DISCONNECT) + return null; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + + try { + dos.writeBytes(signature); + dos.writeShort(protocol_version); + + switch (type) { + case TYPE_MOTION: + dos.writeByte(0); + break; + case TYPE_BUTTON: + dos.writeByte(1); + break; + } + + dos.writeShort(x); + dos.writeShort(y); + dos.writeShort(pressure); + + if (type == Type.TYPE_BUTTON) { + dos.writeByte(button); + dos.writeByte(button_down); + } + } catch(IOException e) { + } + + return baos.toByteArray(); + } } diff --git a/app-android/src/at/bitfire/gfxtablet/NetMotionEvent.java b/app-android/src/at/bitfire/gfxtablet/NetMotionEvent.java deleted file mode 100644 index a17eb32..0000000 --- a/app-android/src/at/bitfire/gfxtablet/NetMotionEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package at.bitfire.gfxtablet; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class NetMotionEvent extends NetEvent { - public NetMotionEvent(int x, int y, int pressure) { - super(x, y, pressure); - } - - @Override - public byte[] toByteArray() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(baos); - - try { - dos.write(0); /* EVENT_TYPE_MOTION */ - dos.writeShort(x); - dos.writeShort(y); - dos.writeShort(pressure); - } catch (IOException e) { - return null; - } - - return baos.toByteArray(); - } -} diff --git a/app-android/src/at/bitfire/gfxtablet/NetworkClient.java b/app-android/src/at/bitfire/gfxtablet/NetworkClient.java index 241a40c..69d5a4c 100644 --- a/app-android/src/at/bitfire/gfxtablet/NetworkClient.java +++ b/app-android/src/at/bitfire/gfxtablet/NetworkClient.java @@ -8,17 +8,17 @@ import java.util.concurrent.LinkedBlockingQueue; import android.content.SharedPreferences; import android.util.Log; - -// see "drivers" directory in Github repository for details about the protocol +import at.bitfire.gfxtablet.NetEvent.Type; public class NetworkClient implements Runnable { + static int GFXTABLET_PORT = 40118; + LinkedBlockingQueue motionQueue = new LinkedBlockingQueue(); LinkedBlockingQueue getQueue() { return motionQueue; } InetAddress destAddress; SharedPreferences preferences; - NetConfigurationEvent lastConfiguration = null; NetworkClient(SharedPreferences preferences) { this.preferences = preferences; @@ -28,10 +28,6 @@ public class NetworkClient implements Runnable { try { String hostName = preferences.getString(SettingsActivity.KEY_PREF_HOST, "unknown.invalid"); destAddress = InetAddress.getByName(hostName); - - if (lastConfiguration != null) - motionQueue.add(lastConfiguration); - } catch (UnknownHostException e) { destAddress = null; return false; @@ -47,18 +43,15 @@ public class NetworkClient implements Runnable { while (true) { NetEvent event = motionQueue.take(); - // save resolution, even if not sending it - if (event.getClass() == NetConfigurationEvent.class) - lastConfiguration = (NetConfigurationEvent)event; // graceful shutdown - else if (event.getClass() == NetDisconnectEvent.class) + if (event.type == Type.TYPE_DISCONNECT) break; if (destAddress == null) // no valid destination host continue; byte[] data = event.toByteArray(); - DatagramPacket pkt = new DatagramPacket(data, data.length, destAddress, 40117); + DatagramPacket pkt = new DatagramPacket(data, data.length, destAddress, GFXTABLET_PORT); socket.send(pkt); } } catch (Exception e) { diff --git a/doc/protocol.txt b/doc/protocol.txt new file mode 100644 index 0000000..cdb7647 --- /dev/null +++ b/doc/protocol.txt @@ -0,0 +1,28 @@ + +Network protocol used by GfxTablet + + + +Version 1 +--------- + +GfxTablet app sends UDP packets to port 40118 of the destination host. + +Packet structure: + + 9 octets "GfxTablet" + 1 ushort version number + 1 ushort type: + 0 motion event (hovering) + 1 button event (finger, pen etc. touches surface) + + 1 ushort x (using full range: 0..65535) + 1 ushort y (using full range: 0..65535) + 1 ushort pressure (using full range 0..65535, 32768 == pressure 1.0f on Android device) + +when type == button event: + 1 byte number of button, starting with 0 + 1 byte button status: + 0 button is down + 1 button is up + diff --git a/driver-uinput/Makefile b/driver-uinput/Makefile index 4f7c547..9370e34 100644 --- a/driver-uinput/Makefile +++ b/driver-uinput/Makefile @@ -1,2 +1,2 @@ -networktablet : networktablet.c +networktablet : networktablet.c protocol.h diff --git a/driver-uinput/networktablet.c b/driver-uinput/networktablet.c index dcd07cd..946f4b3 100644 --- a/driver-uinput/networktablet.c +++ b/driver-uinput/networktablet.c @@ -44,11 +44,11 @@ void init_device(int fd) uidev.id.product = 0x1; uidev.id.version = 1; uidev.absmin[ABS_X] = 0; - uidev.absmax[ABS_X] = INT_MAX; + uidev.absmax[ABS_X] = SHRT_MAX; uidev.absmin[ABS_Y] = 0; - uidev.absmax[ABS_Y] = INT_MAX; + uidev.absmax[ABS_Y] = SHRT_MAX; uidev.absmin[ABS_PRESSURE] = 0; - uidev.absmax[ABS_PRESSURE] = 10000; // 10,000 instead of 32,767 because sometimes there is pressure >1.0 + uidev.absmax[ABS_PRESSURE] = SHRT_MAX/2; if (write(fd, &uidev, sizeof(uidev)) < 0) die("error: write"); @@ -66,7 +66,7 @@ int prepare_socket() bzero(&addr, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; - addr.sin_port = htons(NETWORKTABLET_PORT); + addr.sin_port = htons(GFXTABLET_PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) @@ -90,8 +90,6 @@ int main(void) { int device, socket; struct event_packet ev_pkt; - int x, y, btn_down = 0; - int max_x = INT_MAX, max_y = INT_MAX; if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0) die("error: open"); @@ -99,34 +97,35 @@ int main(void) init_device(device); socket = prepare_socket(); - while (recv(socket, &ev_pkt, sizeof(ev_pkt), 0) >= 7) { // every packet has at least 7 bytes + while (recv(socket, &ev_pkt, sizeof(ev_pkt), 0) >= 9) { // every packet has at least 9 bytes printf("."); fflush(0); + if (memcmp(ev_pkt.signature, "GfxTablet", 9) != 0) { + fprintf(stderr, "\nGot unknown packet on port %i, ignoring\n", GFXTABLET_PORT); + continue; + } + ev_pkt.version = ntohs(ev_pkt.version); + if (ev_pkt.version != PROTOCOL_VERSION) { + fprintf(stderr, "\nGfxTablet app speaks protocol version %i but driver speaks version %i, please update\n", + ev_pkt.version, PROTOCOL_VERSION); + break; + } + ev_pkt.x = ntohs(ev_pkt.x); ev_pkt.y = ntohs(ev_pkt.y); ev_pkt.pressure = ntohs(ev_pkt.pressure); - //printf("x: %hi, y: %hi, pressure: %hi\n", ev_pkt.x, ev_pkt.y, ev_pkt.pressure); + printf("x: %hi, y: %hi, pressure: %hi\n", ev_pkt.x, ev_pkt.y, ev_pkt.pressure); - x = (long)ev_pkt.x * INT_MAX/max_x; - y = (long)ev_pkt.y * INT_MAX/max_y; - - send_event(device, EV_ABS, ABS_X, x); - send_event(device, EV_ABS, ABS_Y, y); + send_event(device, EV_ABS, ABS_X, ev_pkt.x); + send_event(device, EV_ABS, ABS_Y, ev_pkt.y); send_event(device, EV_ABS, ABS_PRESSURE, ev_pkt.pressure); switch (ev_pkt.type) { - case EVENT_TYPE_SET_RESOLUTION: { - struct input_absinfo absinfo; - max_x = ev_pkt.x; - max_y = ev_pkt.y; - printf("Set resolution to %hix%hi\n", max_x+1, max_y+1); - break; - } case EVENT_TYPE_MOTION: send_event(device, EV_SYN, SYN_REPORT, 1); break; case EVENT_TYPE_BUTTON: - if (ev_pkt.button == 1) + if (ev_pkt.button == 0) send_event(device, EV_KEY, BTN_TOUCH, ev_pkt.down); send_event(device, EV_SYN, SYN_REPORT, 1); break; diff --git a/driver-uinput/protocol.h b/driver-uinput/protocol.h index c3c00f6..fc9191f 100644 --- a/driver-uinput/protocol.h +++ b/driver-uinput/protocol.h @@ -1,16 +1,19 @@ -#define NETWORKTABLET_PORT 40117 +#define GFXTABLET_PORT 40118 + +#define PROTOCOL_VERSION 1 #pragma pack(push) #pragma pack(1) -#define EVENT_TYPE_MOTION 0 -#define EVENT_TYPE_BUTTON 1 -#define EVENT_TYPE_SET_RESOLUTION 2 +#define EVENT_TYPE_MOTION 0 +#define EVENT_TYPE_BUTTON 1 struct event_packet { + char signature[9]; + unsigned short version; char type; /* EVENT_TYPE_... */ struct { /* required */ short x, y;