mirror of
https://github.com/rfc2822/GfxTablet
synced 2025-10-03 17:49:17 +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:
parent
54140b9e10
commit
976b64cfd0
8 changed files with 205 additions and 17 deletions
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<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="stylus"/>
|
||||||
<uses-configuration android:reqTouchScreen="finger"/>
|
<uses-configuration android:reqTouchScreen="finger"/>
|
||||||
|
|
|
@ -21,14 +21,18 @@ import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import at.bitfire.gfxtablet.NetEvent.Type;
|
||||||
|
|
||||||
public class CanvasActivity extends AppCompatActivity implements View.OnSystemUiVisibilityChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
public class CanvasActivity extends AppCompatActivity implements View.OnSystemUiVisibilityChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final int RESULT_LOAD_IMAGE = 1;
|
private static final int RESULT_LOAD_IMAGE = 1;
|
||||||
private static final String TAG = "GfxTablet.Canvas";
|
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"));
|
final Uri homepageUri = Uri.parse(("https://gfxtablet.bitfire.at"));
|
||||||
|
|
||||||
NetworkClient netClient;
|
NetworkClient netClient;
|
||||||
|
NetworkServer netServer;
|
||||||
|
|
||||||
SharedPreferences preferences;
|
SharedPreferences preferences;
|
||||||
boolean fullScreen = false;
|
boolean fullScreen = false;
|
||||||
|
@ -37,6 +41,7 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
instance = this;
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
@ -46,6 +51,10 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
// create network client in a separate thread
|
// create network client in a separate thread
|
||||||
netClient = new NetworkClient(PreferenceManager.getDefaultSharedPreferences(this));
|
netClient = new NetworkClient(PreferenceManager.getDefaultSharedPreferences(this));
|
||||||
new Thread(netClient).start();
|
new Thread(netClient).start();
|
||||||
|
// create network server in a separate thread
|
||||||
|
netServer = new NetworkServer(PreferenceManager.getDefaultSharedPreferences(this));
|
||||||
|
new Thread(netServer).start();
|
||||||
|
|
||||||
new ConfigureNetworkingTask().execute();
|
new ConfigureNetworkingTask().execute();
|
||||||
|
|
||||||
// notify CanvasView of the network client
|
// notify CanvasView of the network client
|
||||||
|
@ -77,6 +86,10 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendMotionStopSignal(){
|
||||||
|
netClient.getQueue().add(new NetEvent(Type.TYPE_MOTION, (short) 0, (short) 0, (short) 0));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (fullScreen)
|
if (fullScreen)
|
||||||
|
@ -166,7 +179,7 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearTemplateImage(MenuItem item) {
|
public void clearTemplateImage(MenuItem item) {
|
||||||
preferences.edit().remove(SettingsActivity.KEY_TEMPLATE_IMAGE).commit();
|
preferences.edit().remove(SettingsActivity.KEY_TEMPLATE_IMAGE).apply();
|
||||||
showTemplateImage();
|
showTemplateImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +197,7 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
|
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
|
||||||
String picturePath = cursor.getString(columnIndex);
|
String picturePath = cursor.getString(columnIndex);
|
||||||
|
|
||||||
preferences.edit().putString(SettingsActivity.KEY_TEMPLATE_IMAGE, picturePath).commit();
|
preferences.edit().putString(SettingsActivity.KEY_TEMPLATE_IMAGE, picturePath).apply();
|
||||||
showTemplateImage();
|
showTemplateImage();
|
||||||
} finally {
|
} finally {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
|
@ -192,6 +205,9 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fits chosen image to screen size.
|
||||||
|
*/
|
||||||
public void showTemplateImage() {
|
public void showTemplateImage() {
|
||||||
ImageView template = (ImageView)findViewById(R.id.canvas_template);
|
ImageView template = (ImageView)findViewById(R.id.canvas_template);
|
||||||
template.setImageDrawable(null);
|
template.setImageDrawable(null);
|
||||||
|
@ -200,9 +216,8 @@ public class CanvasActivity extends AppCompatActivity implements View.OnSystemUi
|
||||||
String picturePath = preferences.getString(SettingsActivity.KEY_TEMPLATE_IMAGE, null);
|
String picturePath = preferences.getString(SettingsActivity.KEY_TEMPLATE_IMAGE, null);
|
||||||
if (picturePath != null)
|
if (picturePath != null)
|
||||||
try {
|
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);
|
final Drawable drawable = new BitmapDrawable(getResources(), picturePath);
|
||||||
|
template.setScaleType(ImageView.ScaleType.FIT_XY);
|
||||||
template.setImageDrawable(drawable);
|
template.setImageDrawable(drawable);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
|
|
@ -56,8 +56,8 @@ public class CanvasView extends View implements SharedPreferences.OnSharedPrefer
|
||||||
protected void setBackground() {
|
protected void setBackground() {
|
||||||
if (settings.getBoolean(SettingsActivity.KEY_DARK_CANVAS, false))
|
if (settings.getBoolean(SettingsActivity.KEY_DARK_CANVAS, false))
|
||||||
setBackgroundColor(Color.BLACK);
|
setBackgroundColor(Color.BLACK);
|
||||||
else
|
//else
|
||||||
setBackgroundResource(R.drawable.bg_grid_pattern);
|
//setBackgroundResource(R.drawable.bg_grid_pattern); //<-- Add this as option?
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setInputMethods() {
|
protected void setInputMethods() {
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class NetworkClient implements Runnable {
|
||||||
byte[] data = event.toByteArray();
|
byte[] data = event.toByteArray();
|
||||||
DatagramPacket pkt = new DatagramPacket(data, data.length, destAddress, GFXTABLET_PORT);
|
DatagramPacket pkt = new DatagramPacket(data, data.length, destAddress, GFXTABLET_PORT);
|
||||||
socket.send(pkt);
|
socket.send(pkt);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("GfxTablet", "motionQueue failed: " + e.getMessage());
|
Log.e("GfxTablet", "motionQueue failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Wed Apr 10 15:27:10 PDT 2013
|
#Tue Feb 21 12:16:03 CET 2017
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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)
|
void init_device(int fd)
|
||||||
|
@ -74,12 +88,12 @@ void init_device(int fd)
|
||||||
int prepare_socket()
|
int prepare_socket()
|
||||||
{
|
{
|
||||||
int s;
|
int s;
|
||||||
struct sockaddr_in addr;
|
sockaddr_in addr;
|
||||||
|
|
||||||
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
|
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
|
||||||
die("error: prepare_socket()");
|
die("error: prepare_socket()");
|
||||||
|
|
||||||
bzero(&addr, sizeof(struct sockaddr_in));
|
bzero(&addr, sizeof(sockaddr_in));
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_port = htons(GFXTABLET_PORT);
|
addr.sin_port = htons(GFXTABLET_PORT);
|
||||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
@ -104,11 +118,74 @@ void quit(int signal) {
|
||||||
close(udp_socket);
|
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;
|
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)
|
if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0)
|
||||||
die("error: open");
|
die("error: open");
|
||||||
|
@ -122,7 +199,7 @@ int main(void)
|
||||||
signal(SIGINT, quit);
|
signal(SIGINT, quit);
|
||||||
signal(SIGTERM, 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);
|
printf("."); fflush(0);
|
||||||
|
|
||||||
if (memcmp(ev_pkt.signature, "GfxTablet", 9) != 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);
|
printf("sent button: %hhi, %hhu\n", ev_pkt.button, ev_pkt.down);
|
||||||
send_event(device, EV_SYN, SYN_REPORT, 1);
|
send_event(device, EV_SYN, SYN_REPORT, 1);
|
||||||
break;
|
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);
|
close(udp_socket);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue