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.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-configuration android:reqTouchScreen="stylus"/>
|
||||
<uses-configuration android:reqTouchScreen="finger"/>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||
}
|
||||
}
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue