1
0
Fork 0
mirror of https://github.com/rfc2822/GfxTablet synced 2025-10-03 09:39:16 +02:00

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.
This commit is contained in:
flyingPastaMonster 2017-02-27 16:05:16 +01:00 committed by Stephan Müller
parent 54140b9e10
commit 976b64cfd0
8 changed files with 205 additions and 17 deletions

View file

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-configuration android:reqTouchScreen="stylus"/>
<uses-configuration android:reqTouchScreen="finger"/>

View file

@ -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();

View file

@ -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() {

View file

@ -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());
}

View file

@ -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<byte[]> 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());
}
}
}

View file

@ -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 {

View file

@ -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

View file

@ -1,4 +1,10 @@
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/X.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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);