commit 49b61c7f7ec9d4e49c74c779a2398ef4df4bd504 Author: Richard Date: Tue Jan 15 06:43:50 2013 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23a06c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +### ANDROID + +# built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project + +# Proguard folder generated by Eclipse +proguard/ + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ + + +### ECLIPSE + +*.pydevproject +.project +.metadata +bin/** +tmp/** +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..8223d51 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0797f8e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ + +XorgTablet +========== diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar new file mode 100644 index 0000000..6080877 Binary files /dev/null and b/libs/android-support-v4.jar differ diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..8937e94 --- /dev/null +++ b/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-14 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..22ea2ce Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..b1353fc Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3897345 Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ diff --git a/res/layout/activity_canvas.xml b/res/layout/activity_canvas.xml new file mode 100644 index 0000000..6fd299c --- /dev/null +++ b/res/layout/activity_canvas.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/res/menu/activity_canvas.xml b/res/menu/activity_canvas.xml new file mode 100644 index 0000000..a714644 --- /dev/null +++ b/res/menu/activity_canvas.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml new file mode 100644 index 0000000..541752f --- /dev/null +++ b/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml new file mode 100644 index 0000000..f20e015 --- /dev/null +++ b/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..a6a22bc --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,9 @@ + + + + XorgTablet + Settings + X.org host + Sense stylus only + + \ No newline at end of file diff --git a/res/values/strings_activity_settings.xml b/res/values/strings_activity_settings.xml new file mode 100644 index 0000000..dfc25ca --- /dev/null +++ b/res/values/strings_activity_settings.xml @@ -0,0 +1,14 @@ + + + Settings + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml new file mode 100644 index 0000000..4a10ca4 --- /dev/null +++ b/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/xml/drawing_preferences.xml b/res/xml/drawing_preferences.xml new file mode 100644 index 0000000..586f244 --- /dev/null +++ b/res/xml/drawing_preferences.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/xml/network_preferences.xml b/res/xml/network_preferences.xml new file mode 100644 index 0000000..4c604e1 --- /dev/null +++ b/res/xml/network_preferences.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/xml/preference_headers.xml b/res/xml/preference_headers.xml new file mode 100644 index 0000000..ceb36d7 --- /dev/null +++ b/res/xml/preference_headers.xml @@ -0,0 +1,10 @@ + + +
+ +
+ + \ No newline at end of file diff --git a/src/com/gimpusers/xorgtablet/CanvasActivity.java b/src/com/gimpusers/xorgtablet/CanvasActivity.java new file mode 100644 index 0000000..de73e65 --- /dev/null +++ b/src/com/gimpusers/xorgtablet/CanvasActivity.java @@ -0,0 +1,50 @@ +package com.gimpusers.xorgtablet; + +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.LinearLayout; +import android.widget.Toast; + +public class CanvasActivity extends Activity { + CanvasView canvas; + SharedPreferences prefs; + XorgClient xorgClient; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PreferenceManager.setDefaultValues(this, R.xml.network_preferences, false); + PreferenceManager.setDefaultValues(this, R.xml.drawing_preferences, false); + + setContentView(R.layout.activity_canvas); + LinearLayout layout = (LinearLayout)findViewById(R.id.canvas_layout); + + new Thread(xorgClient = new XorgClient(PreferenceManager.getDefaultSharedPreferences(this))).start(); + + canvas = new CanvasView(this, xorgClient); + layout.addView(canvas); + } + + @Override + protected void onDestroy() { + xorgClient.getQueue().add(new XDisconnectEvent()); + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_canvas, menu); + return true; + } + + public void showSettings(MenuItem item) { + startActivity(new Intent(CanvasActivity.this, SettingsActivity.class)); + } +} diff --git a/src/com/gimpusers/xorgtablet/CanvasView.java b/src/com/gimpusers/xorgtablet/CanvasView.java new file mode 100644 index 0000000..bbca778 --- /dev/null +++ b/src/com/gimpusers/xorgtablet/CanvasView.java @@ -0,0 +1,113 @@ +package com.gimpusers.xorgtablet; + +import java.util.concurrent.ExecutionException; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.AsyncTask; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +@SuppressLint("ViewConstructor") +public class CanvasView extends View implements OnSharedPreferenceChangeListener { + final static int PRESSURE_RESOLUTION = 10000; + + XorgClient xorgClient; + SharedPreferences settings; + boolean acceptStylusOnly; + + public CanvasView(Context context, XorgClient xorgClient) { + super(context); + + // disable until networking has been configured + setEnabled(false); + setBackgroundColor(0xFFD0D0D0); + + settings = PreferenceManager.getDefaultSharedPreferences(context); + settings.registerOnSharedPreferenceChangeListener(this); + reconfigureAcceptedInputDevices(); + + this.xorgClient = xorgClient; + new ConfigureNetworkingTask().execute(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences pref, String key) { + if (key.equals(SettingsActivity.KEY_PREF_HOST)) + new ConfigureNetworkingTask().execute(); + else if (key.equals(SettingsActivity.KEY_PREF_STYLUS_ONLY)) + reconfigureAcceptedInputDevices(); + } + + void reconfigureAcceptedInputDevices() { + acceptStylusOnly = settings.getBoolean(SettingsActivity.KEY_PREF_STYLUS_ONLY, false); + } + + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) { + Toast.makeText(getContext(), String.format("%dx%d", w, h), Toast.LENGTH_SHORT).show(); + xorgClient.getQueue().add(new XConfigurationEvent(w, h, PRESSURE_RESOLUTION)); + } + + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if (isEnabled()) { + for (int ptr = 0; ptr < event.getPointerCount(); ptr++) + 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))); + if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) + xorgClient.getQueue().add(new XMotionEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)event.getPressure(ptr)*PRESSURE_RESOLUTION)); + } + return true; + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isEnabled()) { + for (int ptr = 0; ptr < event.getPointerCount(); ptr++) + if (!acceptStylusOnly || (event.getToolType(ptr) == MotionEvent.TOOL_TYPE_STYLUS)) { + //Log.i("XorgTablet", String.format("Touch event logged: %f|%f, pressure %f", event.getX(ptr), event.getY(ptr), event.getPressure(ptr))); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_MOVE: + xorgClient.getQueue().add(new XMotionEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)event.getPressure(ptr)*PRESSURE_RESOLUTION)); + break; + case MotionEvent.ACTION_DOWN: + xorgClient.getQueue().add(new XButtonEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)event.getPressure(ptr)*PRESSURE_RESOLUTION, true)); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + xorgClient.getQueue().add(new XButtonEvent((int)event.getX(ptr), (int)event.getY(ptr), (int)event.getPressure(ptr)*PRESSURE_RESOLUTION, false)); + break; + } + + } + return true; + } + return false; + } + + + private class ConfigureNetworkingTask extends AsyncTask { + @Override + protected Boolean doInBackground(Void... params) { + return xorgClient.configureNetworking(); + } + + protected void onPostExecute(Boolean success) { + if (success) + setEnabled(true); + else { + setEnabled(false); + Toast.makeText(getContext(), "Unknown host name, network tablet disabled!", Toast.LENGTH_LONG).show(); + } + } + } +} diff --git a/src/com/gimpusers/xorgtablet/SettingsActivity.java b/src/com/gimpusers/xorgtablet/SettingsActivity.java new file mode 100644 index 0000000..4a3282d --- /dev/null +++ b/src/com/gimpusers/xorgtablet/SettingsActivity.java @@ -0,0 +1,64 @@ +package com.gimpusers.xorgtablet; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.RingtonePreference; +import android.text.TextUtils; +import android.view.MenuItem; +import android.support.v4.app.NavUtils; + +import java.util.List; + +public class SettingsActivity extends PreferenceActivity { + public static final String + KEY_PREF_HOST = "host_preference", + KEY_PREF_STYLUS_ONLY = "stylus_only_preference"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.preference_headers, target); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) + finish(); + return false; + } + + + public static class NetworkPrefsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.network_preferences); + } + } + + public static class DrawingPrefsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.drawing_preferences); + } + } +} diff --git a/src/com/gimpusers/xorgtablet/XButtonEvent.java b/src/com/gimpusers/xorgtablet/XButtonEvent.java new file mode 100644 index 0000000..c8f9e7a --- /dev/null +++ b/src/com/gimpusers/xorgtablet/XButtonEvent.java @@ -0,0 +1,34 @@ +package com.gimpusers.xorgtablet; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class XButtonEvent extends XEvent { + boolean down; + + public XButtonEvent(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/src/com/gimpusers/xorgtablet/XConfigurationEvent.java b/src/com/gimpusers/xorgtablet/XConfigurationEvent.java new file mode 100644 index 0000000..2f25092 --- /dev/null +++ b/src/com/gimpusers/xorgtablet/XConfigurationEvent.java @@ -0,0 +1,28 @@ +package com.gimpusers.xorgtablet; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class XConfigurationEvent extends XEvent { + public XConfigurationEvent(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/src/com/gimpusers/xorgtablet/XDisconnectEvent.java b/src/com/gimpusers/xorgtablet/XDisconnectEvent.java new file mode 100644 index 0000000..2625321 --- /dev/null +++ b/src/com/gimpusers/xorgtablet/XDisconnectEvent.java @@ -0,0 +1,12 @@ +package com.gimpusers.xorgtablet; + +public class XDisconnectEvent extends XEvent { + public XDisconnectEvent() { + super(0, 0, 0); + } + + @Override + public byte[] toByteArray() { + return null; + } +} diff --git a/src/com/gimpusers/xorgtablet/XEvent.java b/src/com/gimpusers/xorgtablet/XEvent.java new file mode 100644 index 0000000..dd391f8 --- /dev/null +++ b/src/com/gimpusers/xorgtablet/XEvent.java @@ -0,0 +1,17 @@ +package com.gimpusers.xorgtablet; + +public abstract class XEvent { + int x, y, pressure; + + public int getX() { return x; } + public int getY() { return y; } + public int getPressure() { return pressure; } + + public XEvent(int x, int y, int pressure) { + this.x = Math.max(x, 0); + this.y = Math.max(y, 0); + this.pressure = pressure; + } + + public abstract byte[] toByteArray(); +} diff --git a/src/com/gimpusers/xorgtablet/XMotionEvent.java b/src/com/gimpusers/xorgtablet/XMotionEvent.java new file mode 100644 index 0000000..d9b08e1 --- /dev/null +++ b/src/com/gimpusers/xorgtablet/XMotionEvent.java @@ -0,0 +1,28 @@ +package com.gimpusers.xorgtablet; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class XMotionEvent extends XEvent { + public XMotionEvent(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/src/com/gimpusers/xorgtablet/XorgClient.java b/src/com/gimpusers/xorgtablet/XorgClient.java new file mode 100644 index 0000000..6399bcc --- /dev/null +++ b/src/com/gimpusers/xorgtablet/XorgClient.java @@ -0,0 +1,84 @@ +package com.gimpusers.xorgtablet; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.concurrent.LinkedBlockingQueue; + +import android.app.Service; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Binder; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.provider.ContactsContract.Contacts.Data; +import android.util.Log; +import android.view.MotionEvent; +import android.widget.Toast; + +// see protocol.h in xf86-networktablet driver for details about the protocol + + +public class XorgClient implements Runnable { + LinkedBlockingQueue motionQueue = new LinkedBlockingQueue(); + LinkedBlockingQueue getQueue() { return motionQueue; } + + InetAddress destAddress; + SharedPreferences preferences; + XConfigurationEvent lastConfiguration = null; + + XorgClient(SharedPreferences preferences) { + this.preferences = preferences; + } + + boolean configureNetworking() { + try { + String hostName = preferences.getString(SettingsActivity.KEY_PREF_HOST, "127.0.0.1"); + destAddress = InetAddress.getByName(hostName); + + if (lastConfiguration != null) + motionQueue.add(lastConfiguration); + + } catch (UnknownHostException e) { + destAddress = null; + return false; + } + return true; + } + + @Override + public void run() { + try { + DatagramSocket socket = new DatagramSocket(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + + while (true) { + XEvent event = motionQueue.take(); + + if (destAddress == null) + continue; + + if (event.getClass() == XConfigurationEvent.class) + lastConfiguration = (XConfigurationEvent)event; + else if (event.getClass() == XDisconnectEvent.class) + break; + + byte[] data = event.toByteArray(); + DatagramPacket pkt = new DatagramPacket(data, data.length, destAddress, 40117); + socket.send(pkt); + + baos.reset(); + } + } catch (Exception e) { + Log.e("XorgTablet", "motionQueue failed: " + e.getMessage()); + } + } +}