mirror of
https://github.com/rfc2822/GfxTablet
synced 2025-10-03 09:39:16 +02:00
* driver, app: new protocol
* new protocol specification that allows versioning (version 1) * App: refactoring, version: 1.1
This commit is contained in:
parent
38d99623df
commit
5f1d8f29d8
14 changed files with 166 additions and 163 deletions
|
@ -18,7 +18,7 @@ It consists of two components:
|
||||||
* the input driver for your PC
|
* the input driver for your PC
|
||||||
|
|
||||||
The GfxTablet app sends motion and touch events via UDP to a specified host
|
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"
|
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.
|
on your PC that is controlled by your Android device.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="at.bitfire.gfxtablet"
|
package="at.bitfire.gfxtablet"
|
||||||
android:versionCode="1"
|
android:versionCode="2"
|
||||||
android:versionName="1.0" >
|
android:versionName="1.1" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="14"
|
android:minSdkVersion="14"
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class CanvasActivity extends Activity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
netClient.getQueue().add(new NetDisconnectEvent());
|
netClient.getQueue().add(new NetEvent(NetEvent.Type.TYPE_DISCONNECT));
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package at.bitfire.gfxtablet;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
@ -9,14 +10,14 @@ import android.preference.PreferenceManager;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import at.bitfire.gfxtablet.NetEvent.Type;
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
public class CanvasView extends View implements OnSharedPreferenceChangeListener {
|
public class CanvasView extends View implements OnSharedPreferenceChangeListener {
|
||||||
final static int PRESSURE_RESOLUTION = 10000;
|
|
||||||
|
|
||||||
NetworkClient netClient;
|
NetworkClient netClient;
|
||||||
SharedPreferences settings;
|
SharedPreferences settings;
|
||||||
boolean acceptStylusOnly;
|
boolean acceptStylusOnly;
|
||||||
|
int maxX, maxY;
|
||||||
|
|
||||||
public CanvasView(Context context, NetworkClient netClient) {
|
public CanvasView(Context context, NetworkClient netClient) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -47,8 +48,8 @@ public class CanvasView extends View implements OnSharedPreferenceChangeListener
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
|
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
|
||||||
Toast.makeText(getContext(), String.format("%dx%d", w, h), Toast.LENGTH_SHORT).show();
|
maxX = w;
|
||||||
netClient.getQueue().add(new NetConfigurationEvent(w, h, PRESSURE_RESOLUTION));
|
maxY = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +60,11 @@ public class CanvasView extends View implements OnSharedPreferenceChangeListener
|
||||||
if (!acceptStylusOnly || (event.getToolType(ptr) == MotionEvent.TOOL_TYPE_STYLUS)) {
|
if (!acceptStylusOnly || (event.getToolType(ptr) == MotionEvent.TOOL_TYPE_STYLUS)) {
|
||||||
//Log.i("XorgTablet", String.format("Generic motion event logged: %f|%f, pressure %f", event.getX(ptr), event.getY(ptr), event.getPressure(ptr)));
|
//Log.i("XorgTablet", String.format("Generic motion event logged: %f|%f, pressure %f", event.getX(ptr), event.getY(ptr), event.getPressure(ptr)));
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE)
|
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE)
|
||||||
netClient.getQueue().add(new NetMotionEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)(event.getPressure(ptr)*PRESSURE_RESOLUTION)));
|
netClient.getQueue().add(new NetEvent(Type.TYPE_MOTION,
|
||||||
|
normalizeX(event.getX(ptr)),
|
||||||
|
normalizeY(event.getY(ptr)),
|
||||||
|
normalizePressure(event.getPressure(ptr))
|
||||||
|
));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -71,17 +76,20 @@ public class CanvasView extends View implements OnSharedPreferenceChangeListener
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
for (int ptr = 0; ptr < event.getPointerCount(); ptr++)
|
for (int ptr = 0; ptr < event.getPointerCount(); ptr++)
|
||||||
if (!acceptStylusOnly || (event.getToolType(ptr) == MotionEvent.TOOL_TYPE_STYLUS)) {
|
if (!acceptStylusOnly || (event.getToolType(ptr) == MotionEvent.TOOL_TYPE_STYLUS)) {
|
||||||
|
short nx = normalizeX(event.getX(ptr)),
|
||||||
|
ny = normalizeY(event.getY(ptr)),
|
||||||
|
npressure = normalizePressure(event.getPressure(ptr));
|
||||||
//Log.i("XorgTablet", String.format("Touch event logged: %f|%f, pressure %f", event.getX(ptr), event.getY(ptr), event.getPressure(ptr)));
|
//Log.i("XorgTablet", String.format("Touch event logged: %f|%f, pressure %f", event.getX(ptr), event.getY(ptr), event.getPressure(ptr)));
|
||||||
switch (event.getActionMasked()) {
|
switch (event.getActionMasked()) {
|
||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
netClient.getQueue().add(new NetMotionEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)(event.getPressure(ptr)*PRESSURE_RESOLUTION)));
|
netClient.getQueue().add(new NetEvent(Type.TYPE_MOTION, nx, ny, npressure));
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
netClient.getQueue().add(new NetButtonEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)(event.getPressure(ptr)*PRESSURE_RESOLUTION), true));
|
netClient.getQueue().add(new NetEvent(Type.TYPE_BUTTON, nx, ny, npressure, 0, true));
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
case MotionEvent.ACTION_CANCEL:
|
case MotionEvent.ACTION_CANCEL:
|
||||||
netClient.getQueue().add(new NetButtonEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)(event.getPressure(ptr)*PRESSURE_RESOLUTION), false));
|
netClient.getQueue().add(new NetEvent(Type.TYPE_BUTTON, nx, ny, npressure, 0, false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +100,19 @@ public class CanvasView extends View implements OnSharedPreferenceChangeListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
short normalizeX(float x) {
|
||||||
|
return (short)(Math.min(Math.max(0, x), maxX) * Short.MAX_VALUE/maxX);
|
||||||
|
}
|
||||||
|
|
||||||
|
short normalizeY(float x) {
|
||||||
|
return (short)(Math.min(Math.max(0, x), maxY) * Short.MAX_VALUE/maxY);
|
||||||
|
}
|
||||||
|
|
||||||
|
short normalizePressure(float x) {
|
||||||
|
return (short)(Math.min(Math.max(0, x), 2.0) * Short.MAX_VALUE/2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class ConfigureNetworkingTask extends AsyncTask<Void, Void, Boolean> {
|
private class ConfigureNetworkingTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
@ -103,7 +124,8 @@ public class CanvasView extends View implements OnSharedPreferenceChangeListener
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
else {
|
else {
|
||||||
setEnabled(false);
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +1,77 @@
|
||||||
package at.bitfire.gfxtablet;
|
package at.bitfire.gfxtablet;
|
||||||
|
|
||||||
public abstract class NetEvent {
|
import java.io.ByteArrayOutputStream;
|
||||||
int x, y, pressure;
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public int getX() { return x; }
|
public class NetEvent {
|
||||||
public int getY() { return y; }
|
enum Type {
|
||||||
public int getPressure() { return pressure; }
|
TYPE_MOTION,
|
||||||
|
TYPE_BUTTON,
|
||||||
|
|
||||||
public NetEvent(int x, int y, int pressure) {
|
// not specified in protocol, only needed to shut down network thread
|
||||||
this.x = Math.max(x, 0);
|
TYPE_DISCONNECT
|
||||||
this.y = Math.max(y, 0);
|
};
|
||||||
|
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;
|
this.pressure = pressure;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract byte[] toByteArray();
|
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 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,17 +8,17 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import at.bitfire.gfxtablet.NetEvent.Type;
|
||||||
// see "drivers" directory in Github repository for details about the protocol
|
|
||||||
|
|
||||||
|
|
||||||
public class NetworkClient implements Runnable {
|
public class NetworkClient implements Runnable {
|
||||||
|
static int GFXTABLET_PORT = 40118;
|
||||||
|
|
||||||
LinkedBlockingQueue<NetEvent> motionQueue = new LinkedBlockingQueue<NetEvent>();
|
LinkedBlockingQueue<NetEvent> motionQueue = new LinkedBlockingQueue<NetEvent>();
|
||||||
LinkedBlockingQueue<NetEvent> getQueue() { return motionQueue; }
|
LinkedBlockingQueue<NetEvent> getQueue() { return motionQueue; }
|
||||||
|
|
||||||
InetAddress destAddress;
|
InetAddress destAddress;
|
||||||
SharedPreferences preferences;
|
SharedPreferences preferences;
|
||||||
NetConfigurationEvent lastConfiguration = null;
|
|
||||||
|
|
||||||
NetworkClient(SharedPreferences preferences) {
|
NetworkClient(SharedPreferences preferences) {
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
|
@ -28,10 +28,6 @@ public class NetworkClient implements Runnable {
|
||||||
try {
|
try {
|
||||||
String hostName = preferences.getString(SettingsActivity.KEY_PREF_HOST, "unknown.invalid");
|
String hostName = preferences.getString(SettingsActivity.KEY_PREF_HOST, "unknown.invalid");
|
||||||
destAddress = InetAddress.getByName(hostName);
|
destAddress = InetAddress.getByName(hostName);
|
||||||
|
|
||||||
if (lastConfiguration != null)
|
|
||||||
motionQueue.add(lastConfiguration);
|
|
||||||
|
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
destAddress = null;
|
destAddress = null;
|
||||||
return false;
|
return false;
|
||||||
|
@ -47,18 +43,15 @@ public class NetworkClient implements Runnable {
|
||||||
while (true) {
|
while (true) {
|
||||||
NetEvent event = motionQueue.take();
|
NetEvent event = motionQueue.take();
|
||||||
|
|
||||||
// save resolution, even if not sending it
|
|
||||||
if (event.getClass() == NetConfigurationEvent.class)
|
|
||||||
lastConfiguration = (NetConfigurationEvent)event;
|
|
||||||
// graceful shutdown
|
// graceful shutdown
|
||||||
else if (event.getClass() == NetDisconnectEvent.class)
|
if (event.type == Type.TYPE_DISCONNECT)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (destAddress == null) // no valid destination host
|
if (destAddress == null) // no valid destination host
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
byte[] data = event.toByteArray();
|
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);
|
socket.send(pkt);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
28
doc/protocol.txt
Normal file
28
doc/protocol.txt
Normal file
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
networktablet : networktablet.c
|
networktablet : networktablet.c protocol.h
|
||||||
|
|
|
@ -44,11 +44,11 @@ void init_device(int fd)
|
||||||
uidev.id.product = 0x1;
|
uidev.id.product = 0x1;
|
||||||
uidev.id.version = 1;
|
uidev.id.version = 1;
|
||||||
uidev.absmin[ABS_X] = 0;
|
uidev.absmin[ABS_X] = 0;
|
||||||
uidev.absmax[ABS_X] = INT_MAX;
|
uidev.absmax[ABS_X] = SHRT_MAX;
|
||||||
uidev.absmin[ABS_Y] = 0;
|
uidev.absmin[ABS_Y] = 0;
|
||||||
uidev.absmax[ABS_Y] = INT_MAX;
|
uidev.absmax[ABS_Y] = SHRT_MAX;
|
||||||
uidev.absmin[ABS_PRESSURE] = 0;
|
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)
|
if (write(fd, &uidev, sizeof(uidev)) < 0)
|
||||||
die("error: write");
|
die("error: write");
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ int prepare_socket()
|
||||||
|
|
||||||
bzero(&addr, sizeof(struct sockaddr_in));
|
bzero(&addr, sizeof(struct sockaddr_in));
|
||||||
addr.sin_family = AF_INET;
|
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);
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
|
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
|
||||||
|
@ -90,8 +90,6 @@ int main(void)
|
||||||
{
|
{
|
||||||
int device, socket;
|
int device, socket;
|
||||||
struct event_packet ev_pkt;
|
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)
|
if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0)
|
||||||
die("error: open");
|
die("error: open");
|
||||||
|
@ -99,34 +97,35 @@ int main(void)
|
||||||
init_device(device);
|
init_device(device);
|
||||||
socket = prepare_socket();
|
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);
|
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.x = ntohs(ev_pkt.x);
|
||||||
ev_pkt.y = ntohs(ev_pkt.y);
|
ev_pkt.y = ntohs(ev_pkt.y);
|
||||||
ev_pkt.pressure = ntohs(ev_pkt.pressure);
|
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;
|
send_event(device, EV_ABS, ABS_X, ev_pkt.x);
|
||||||
y = (long)ev_pkt.y * INT_MAX/max_y;
|
send_event(device, EV_ABS, ABS_Y, ev_pkt.y);
|
||||||
|
|
||||||
send_event(device, EV_ABS, ABS_X, x);
|
|
||||||
send_event(device, EV_ABS, ABS_Y, y);
|
|
||||||
send_event(device, EV_ABS, ABS_PRESSURE, ev_pkt.pressure);
|
send_event(device, EV_ABS, ABS_PRESSURE, ev_pkt.pressure);
|
||||||
|
|
||||||
switch (ev_pkt.type) {
|
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:
|
case EVENT_TYPE_MOTION:
|
||||||
send_event(device, EV_SYN, SYN_REPORT, 1);
|
send_event(device, EV_SYN, SYN_REPORT, 1);
|
||||||
break;
|
break;
|
||||||
case EVENT_TYPE_BUTTON:
|
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_KEY, BTN_TOUCH, ev_pkt.down);
|
||||||
send_event(device, EV_SYN, SYN_REPORT, 1);
|
send_event(device, EV_SYN, SYN_REPORT, 1);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
|
|
||||||
#define NETWORKTABLET_PORT 40117
|
#define GFXTABLET_PORT 40118
|
||||||
|
|
||||||
|
#define PROTOCOL_VERSION 1
|
||||||
|
|
||||||
|
|
||||||
#pragma pack(push)
|
#pragma pack(push)
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
|
|
||||||
#define EVENT_TYPE_MOTION 0
|
#define EVENT_TYPE_MOTION 0
|
||||||
#define EVENT_TYPE_BUTTON 1
|
#define EVENT_TYPE_BUTTON 1
|
||||||
#define EVENT_TYPE_SET_RESOLUTION 2
|
|
||||||
|
|
||||||
struct event_packet
|
struct event_packet
|
||||||
{
|
{
|
||||||
|
char signature[9];
|
||||||
|
unsigned short version;
|
||||||
char type; /* EVENT_TYPE_... */
|
char type; /* EVENT_TYPE_... */
|
||||||
struct { /* required */
|
struct { /* required */
|
||||||
short x, y;
|
short x, y;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue