From 976b64cfd05053199f33a3b55c7f2aa6f25f03d1 Mon Sep 17 00:00:00 2001 From: flyingPastaMonster Date: Mon, 27 Feb 2017 16:05:16 +0100 Subject: [PATCH] Enable to see where and what you have drawn If an ending event is detected, the drive will take a screenshot, and sends it back to the app. The client will than put it as its background image. Android app changes: * Added write permission to the app. * Added method to get the canvas object from anywhere. * Added network server to retrieve the image. * Added method to send a atop motion event signal, which is needed to trigger the driver to send a new screenshot. * Changed commit to apply because it is deprecated. * The template image will now always fit the screen. (Removed TODO) * Commented out grid pattern. * Updated gradle version. Driver changes: * Included a lot of new library's, not sure if every is needed right now. * Added type definition for some structures. * Added function to sleep in ms. * Added function for the screenshot sending thread. * Changed recv to recvfrom to get the IP of the android device in order to send back the screenshot. TODO: * Show the cursor path in the app. * Sort out driver library's. * Add the picture path to the option dialog. * Create the screenshot in /tmp with a more suitable name. * Sort functions that are not needed anymore. --- app-android/app/src/main/AndroidManifest.xml | 1 + .../at/bitfire/gfxtablet/CanvasActivity.java | 23 ++++- .../java/at/bitfire/gfxtablet/CanvasView.java | 4 +- .../at/bitfire/gfxtablet/NetworkClient.java | 2 +- .../at/bitfire/gfxtablet/NetworkServer.java | 89 +++++++++++++++++ app-android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- driver-uinput/networktablet.c | 97 +++++++++++++++++-- 8 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkServer.java diff --git a/app-android/app/src/main/AndroidManifest.xml b/app-android/app/src/main/AndroidManifest.xml index 225ca27..a0a3b2c 100644 --- a/app-android/app/src/main/AndroidManifest.xml +++ b/app-android/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + diff --git a/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasActivity.java b/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasActivity.java index e961564..22761d6 100644 --- a/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasActivity.java +++ b/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasActivity.java @@ -21,14 +21,18 @@ import android.view.View; import android.view.WindowManager; import android.widget.ImageView; import android.widget.Toast; +import at.bitfire.gfxtablet.NetEvent.Type; public class CanvasActivity extends AppCompatActivity implements View.OnSystemUiVisibilityChangeListener, SharedPreferences.OnSharedPreferenceChangeListener { private static final int RESULT_LOAD_IMAGE = 1; private static final String TAG = "GfxTablet.Canvas"; + private static CanvasActivity instance; + public static CanvasActivity get() { return instance; } final Uri homepageUri = Uri.parse(("https://gfxtablet.bitfire.at")); NetworkClient netClient; + NetworkServer netServer; SharedPreferences preferences; boolean fullScreen = false; @@ -37,6 +41,7 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + instance = this; preferences = PreferenceManager.getDefaultSharedPreferences(this); preferences.registerOnSharedPreferenceChangeListener(this); @@ -46,6 +51,10 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi // create network client in a separate thread netClient = new NetworkClient(PreferenceManager.getDefaultSharedPreferences(this)); new Thread(netClient).start(); + // create network server in a separate thread + netServer = new NetworkServer(PreferenceManager.getDefaultSharedPreferences(this)); + new Thread(netServer).start(); + new ConfigureNetworkingTask().execute(); // notify CanvasView of the network client @@ -77,6 +86,10 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi return true; } + public void sendMotionStopSignal(){ + netClient.getQueue().add(new NetEvent(Type.TYPE_MOTION, (short) 0, (short) 0, (short) 0)); + } + @Override public void onBackPressed() { if (fullScreen) @@ -166,7 +179,7 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi } public void clearTemplateImage(MenuItem item) { - preferences.edit().remove(SettingsActivity.KEY_TEMPLATE_IMAGE).commit(); + preferences.edit().remove(SettingsActivity.KEY_TEMPLATE_IMAGE).apply(); showTemplateImage(); } @@ -184,7 +197,7 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String picturePath = cursor.getString(columnIndex); - preferences.edit().putString(SettingsActivity.KEY_TEMPLATE_IMAGE, picturePath).commit(); + preferences.edit().putString(SettingsActivity.KEY_TEMPLATE_IMAGE, picturePath).apply(); showTemplateImage(); } finally { cursor.close(); @@ -192,6 +205,9 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi } } + /** + * Fits chosen image to screen size. + */ public void showTemplateImage() { ImageView template = (ImageView)findViewById(R.id.canvas_template); template.setImageDrawable(null); @@ -200,9 +216,8 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi String picturePath = preferences.getString(SettingsActivity.KEY_TEMPLATE_IMAGE, null); if (picturePath != null) try { - // TODO load bitmap efficiently, for intended view size and display resolution - // https://developer.android.com/training/displaying-bitmaps/load-bitmap.html final Drawable drawable = new BitmapDrawable(getResources(), picturePath); + template.setScaleType(ImageView.ScaleType.FIT_XY); template.setImageDrawable(drawable); } catch (Exception e) { Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); diff --git a/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasView.java b/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasView.java index 8cc27bb..fffd108 100644 --- a/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasView.java +++ b/app-android/app/src/main/java/at/bitfire/gfxtablet/CanvasView.java @@ -56,8 +56,8 @@ public class CanvasView extends View implements SharedPreferences.OnSharedPrefer protected void setBackground() { if (settings.getBoolean(SettingsActivity.KEY_DARK_CANVAS, false)) setBackgroundColor(Color.BLACK); - else - setBackgroundResource(R.drawable.bg_grid_pattern); + //else + //setBackgroundResource(R.drawable.bg_grid_pattern); //<-- Add this as option? } protected void setInputMethods() { diff --git a/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkClient.java b/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkClient.java index c437725..32a6264 100644 --- a/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkClient.java +++ b/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkClient.java @@ -54,7 +54,7 @@ public class NetworkClient implements Runnable { byte[] data = event.toByteArray(); DatagramPacket pkt = new DatagramPacket(data, data.length, destAddress, GFXTABLET_PORT); socket.send(pkt); - } + } } catch (Exception e) { Log.e("GfxTablet", "motionQueue failed: " + e.getMessage()); } diff --git a/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkServer.java b/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkServer.java new file mode 100644 index 0000000..0f3a44b --- /dev/null +++ b/app-android/app/src/main/java/at/bitfire/gfxtablet/NetworkServer.java @@ -0,0 +1,89 @@ +package at.bitfire.gfxtablet; + +import android.content.SharedPreferences; +import android.util.Log; +import android.util.SparseArray; + +import java.io.File; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.io.FileOutputStream; + +public class NetworkServer implements Runnable { + static final int GFXTABLET_PORT = 40118; + NetworkClient netClient; + final SharedPreferences preferences; + + NetworkServer(SharedPreferences preferences) { + this.preferences = preferences; + } + + @Override + public void run() { + try { + DatagramSocket socket = new DatagramSocket(GFXTABLET_PORT); + + SparseArray buffer = new SparseArray<>(); + // Init has to be done twice because the first call will be set on the server with 0.0.0.0 + // but we nee the ip of the client. + CanvasActivity.get().sendMotionStopSignal(); + CanvasActivity.get().sendMotionStopSignal(); + while (true) { + byte[] buf = new byte[60030]; + DatagramPacket packet = new DatagramPacket(buf, buf.length); + socket.receive(packet); + int n = buf[60029]; + Log.i("receive:", String.valueOf(n)); + if (n != 0){ + buffer.put(n, buf); + } else if (buffer.size() > 0 ) { + try { + String path = CanvasActivity.get().getFilesDir().getPath() + "/desktop.png"; + path = "/storage/emulated/0/test.png"; //TODO set via options + Log.i("buffer:", String.valueOf(buf[0])); + boolean parts = buffer.size() == (int) buf[0]; + for (int i=0; i < buf[0]; i++) { + if (parts) { + Log.i("keyAt " + i, String.valueOf(buffer.keyAt(i))); + parts = buffer.keyAt(i) == i+1; + } + } + if (!parts) { + buffer.clear(); + CanvasActivity.get().sendMotionStopSignal(); + Log.i("Image Problem", "tying to refetch the screenshot"); + continue; + } + Log.i("receive", "completed with " + buffer.size()); + + FileOutputStream fos = new FileOutputStream(path); + for (int i=1; i <= buffer.size(); i++) { + fos.write(buffer.get(i), 0, 60000); + } + fos.flush(); + fos.close(); + File file = new File(path); + long size = file.length(); + Log.i("file-path", path); + Log.i("file-size", String.valueOf(size)); + Log.i("file-path-current", preferences.getString(SettingsActivity.KEY_TEMPLATE_IMAGE, null)); + preferences.edit().putString(SettingsActivity.KEY_TEMPLATE_IMAGE, path).apply(); + CanvasActivity.get().runOnUiThread(new Runnable() { + @Override + public void run() { + CanvasActivity.get().showTemplateImage(); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + //compile image and set it + buffer.clear(); + } + } + } catch (Exception e) { + Log.i("GfxTablet", "Screenshot server failed: " + e.getMessage()); + } + } +} diff --git a/app-android/build.gradle b/app-android/build.gradle index 0209e1e..8874234 100644 --- a/app-android/build.gradle +++ b/app-android/build.gradle @@ -4,7 +4,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:2.2.3' } } allprojects { diff --git a/app-android/gradle/wrapper/gradle-wrapper.properties b/app-android/gradle/wrapper/gradle-wrapper.properties index 0c71e76..74ef61c 100644 --- a/app-android/gradle/wrapper/gradle-wrapper.properties +++ b/app-android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Tue Feb 21 12:16:03 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/driver-uinput/networktablet.c b/driver-uinput/networktablet.c index 93cab80..5a51bc1 100644 --- a/driver-uinput/networktablet.c +++ b/driver-uinput/networktablet.c @@ -1,4 +1,10 @@ +#include +#include +#include +#include +#include +#include #include #include #include @@ -19,7 +25,15 @@ } -int udp_socket; +int udp_socket, sending; + +typedef struct event_packet event_packet; +typedef struct sockaddr_in sockaddr_in; + +typedef struct sending_t { + sockaddr_in from; + int slen; +} sending_t; void init_device(int fd) @@ -74,12 +88,12 @@ void init_device(int fd) int prepare_socket() { int s; - struct sockaddr_in addr; + sockaddr_in addr; if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) die("error: prepare_socket()"); - bzero(&addr, sizeof(struct sockaddr_in)); + bzero(&addr, sizeof(sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(GFXTABLET_PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); @@ -104,11 +118,74 @@ void quit(int signal) { close(udp_socket); } +void msleep(int ms){ + struct timespec req = {0}; + req.tv_sec = 0; + req.tv_nsec = ms * 1000000L; + nanosleep(&req, (struct timespec *)NULL); +} -int main(void) -{ +void *send_current_screen(void *arg){ + sending_t *args = (sending_t*) arg; + sockaddr_in from = args->from; + int slen = args->slen; + Display *disp = XOpenDisplay(":0"); + + if (sending || !disp) { + return NULL; + } + printf("\nsend_thread\n"); + + msleep(300); + sending=1; + Window root; + cairo_surface_t *surface; + int scr; + scr = DefaultScreen(disp); + root = DefaultRootWindow(disp); + /* get the root surface on given display */ + surface = cairo_xlib_surface_create(disp, root, DefaultVisual(disp, scr), + DisplayWidth(disp, scr), + DisplayHeight(disp, scr)); + /* right now, the tool only outputs PNG images */ + cairo_surface_write_to_png( surface, "test.png" ); + /* free the memory*/ + cairo_surface_destroy(surface); + + FILE *istream; + if ( (istream = fopen("test.png", "r" ) ) == NULL ){ + die("file non-existant!"); + } + from.sin_port = htons(GFXTABLET_PORT); + int max=60000; + char buff[max+30]; + int n=1; + while(fread(buff,sizeof(char),max,istream) != 0){ + printf("Send packet to %s:%d\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port)); + buff[max +29] = n; + if (sendto(udp_socket, buff, sizeof(buff), 0, (struct sockaddr*) &from, slen) == -1){ + die("sendto()"); + } + n++; + for (int r = 0; r <= max ; r++){ + buff[r] = 0; + } + } + fclose (istream ); + char buf[1]; + buf[0] = n-1; + if (sendto(udp_socket, buf, strlen(buff)+1, 0, (struct sockaddr*) &from, slen) == -1){ + die("sendto()"); + } + sending=0; +} + +int main(void){ int device; - struct event_packet ev_pkt; + sending=0; + event_packet ev_pkt; + sending_t sock_t; + pthread_t ev_retreive_t, screen_send_t; if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0) die("error: open"); @@ -122,7 +199,7 @@ int main(void) signal(SIGINT, quit); signal(SIGTERM, quit); - while (recv(udp_socket, &ev_pkt, sizeof(ev_pkt), 0) >= 9) { // every packet has at least 9 bytes + while (recvfrom(udp_socket, &ev_pkt, sizeof(event_packet), 0, (struct sockaddr *) &sock_t.from, &sock_t.slen) >= 9) { // every packet has at least 9 bytes printf("."); fflush(0); if (memcmp(ev_pkt.signature, "GfxTablet", 9) != 0) { @@ -165,7 +242,13 @@ int main(void) printf("sent button: %hhi, %hhu\n", ev_pkt.button, ev_pkt.down); send_event(device, EV_SYN, SYN_REPORT, 1); break; + } + if (ev_pkt.pressure == 0 && memcmp(inet_ntoa(sock_t.from.sin_addr), "0.0.0.0", 7) != 0) { + if(pthread_create(&screen_send_t, NULL, send_current_screen, &sock_t)) { + fprintf(stderr, "Error creating thread\n"); + return 1; + } } } close(udp_socket);