diff --git a/app/libs/com.antlersoft.android.contentxml.jar b/app/libs/com.antlersoft.android.contentxml.jar
new file mode 100644
index 000000000..6e8fd20bd
Binary files /dev/null and b/app/libs/com.antlersoft.android.contentxml.jar differ
diff --git a/app/libs/com.antlersoft.android.db.jar b/app/libs/com.antlersoft.android.db.jar
new file mode 100644
index 000000000..e037aa136
Binary files /dev/null and b/app/libs/com.antlersoft.android.db.jar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d206e58ab..acf3d9dfc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -55,6 +55,12 @@
android:screenOrientation="sensorLandscape"
android:name=".prefs.LauncherPreferenceActivity"
android:configChanges="keyboardHidden|orientation|screenSize"/>
+
+
+
diff --git a/app/src/main/java/android/androidVNC/AbstractBitmapData.java b/app/src/main/java/android/androidVNC/AbstractBitmapData.java
new file mode 100644
index 000000000..109166685
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractBitmapData.java
@@ -0,0 +1,171 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.io.IOException;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.widget.ImageView;
+
+/**
+ * Abstract interface between the VncCanvas and the bitmap and pixel data buffers that actually contain
+ * the data.
+ * This allows for implementations that use smaller bitmaps or buffers to save memory.
+ * @author Michael A. MacDonald
+ *
+ */
+abstract class AbstractBitmapData {
+ int framebufferwidth;
+ int framebufferheight;
+ int bitmapwidth;
+ int bitmapheight;
+ RfbProto rfb;
+ Bitmap mbitmap;
+ int bitmapPixels[];
+ Canvas memGraphics;
+ boolean waitingForInput;
+ VncCanvas vncCanvas;
+ private AbstractBitmapDrawable drawable;
+
+ AbstractBitmapData( RfbProto p, VncCanvas c)
+ {
+ rfb=p;
+ vncCanvas = c;
+ framebufferwidth=rfb.framebufferWidth;
+ framebufferheight=rfb.framebufferHeight;
+ }
+
+ synchronized void doneWaiting()
+ {
+ waitingForInput=false;
+ }
+
+ final void invalidateMousePosition()
+ {
+ if (vncCanvas.connection.getUseLocalCursor())
+ {
+ if (drawable==null)
+ drawable = createDrawable();
+ drawable.setCursorRect(vncCanvas.mouseX,vncCanvas.mouseY);
+ vncCanvas.invalidate(drawable.cursorRect);
+ }
+ }
+
+ /**
+ *
+ * @return The smallest scale supported by the implementation; the scale at which
+ * the bitmap would be smaller than the screen
+ */
+ float getMinimumScale()
+ {
+ double scale = 0.75;
+ int displayWidth = vncCanvas.getWidth();
+ int displayHeight = vncCanvas.getHeight();
+ for (; scale >= 0; scale -= 0.25)
+ {
+ if (scale * bitmapwidth < displayWidth || scale * bitmapheight < displayHeight)
+ break;
+ }
+ return (float)(scale + 0.25);
+ }
+
+ /**
+ * Send a request through the protocol to get the data for the currently held bitmap
+ * @param incremental True if we want incremental update; false for full update
+ */
+ abstract void writeFullUpdateRequest( boolean incremental) throws IOException;
+
+ /**
+ * Determine if a rectangle in full-frame coordinates can be drawn in the existing buffer
+ * @param x Top left x
+ * @param y Top left y
+ * @param w width (pixels)
+ * @param h height (pixels)
+ * @return True if entire rectangle fits into current screen buffer, false otherwise
+ */
+ abstract boolean validDraw( int x, int y, int w, int h);
+
+ /**
+ * Return an offset in the bitmapPixels array of a point in full-frame coordinates
+ * @param x
+ * @param y
+ * @return Offset in bitmapPixels array of color data for that point
+ */
+ abstract int offset( int x, int y);
+
+ /**
+ * Update pixels in the bitmap with data from the bitmapPixels array, positioned
+ * in full-frame coordinates
+ * @param x Top left x
+ * @param y Top left y
+ * @param w width (pixels)
+ * @param h height (pixels)
+ */
+ abstract void updateBitmap( int x, int y, int w, int h);
+
+ /**
+ * Create drawable appropriate for this data
+ * @return drawable
+ */
+ abstract AbstractBitmapDrawable createDrawable();
+
+ /**
+ * Call in UI thread; tell ImageView we've changed
+ * @param v ImageView displaying bitmap data
+ */
+ void updateView(ImageView v)
+ {
+ if (drawable==null)
+ drawable = createDrawable();
+ v.setImageDrawable(drawable);
+ v.invalidate();
+ }
+
+ /**
+ * Copy a rectangle from one part of the bitmap to another
+ * @param src Rectangle in full-frame coordinates to be copied
+ * @param dest Destination rectangle in full-frame coordinates
+ * @param paint Paint specifier
+ */
+ abstract void copyRect( Rect src, Rect dest, Paint paint);
+
+ /**
+ * Draw a rectangle in the bitmap with coordinates given in full frame
+ * @param x Top left x
+ * @param y Top left y
+ * @param w width (pixels)
+ * @param h height (pixels)
+ * @param paint How to draw
+ */
+ abstract void drawRect( int x, int y, int w, int h, Paint paint);
+
+ /**
+ * Scroll position has changed.
+ *
+ * This method is called in the UI thread-- it updates internal status, but does
+ * not change the bitmap data or send a network request until syncScroll is called
+ * @param newx Position of left edge of visible part in full-frame coordinates
+ * @param newy Position of top edge of visible part in full-frame coordinates
+ */
+ abstract void scrollChanged( int newx, int newy);
+
+ /**
+ * Sync scroll -- called from network thread; copies scroll changes from UI to network state
+ */
+ abstract void syncScroll();
+
+ /**
+ * Release resources
+ */
+ void dispose()
+ {
+ if ( mbitmap!=null )
+ mbitmap.recycle();
+ memGraphics = null;
+ bitmapPixels = null;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractBitmapDrawable.java b/app/src/main/java/android/androidVNC/AbstractBitmapDrawable.java
new file mode 100644
index 000000000..a311cce8a
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractBitmapDrawable.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.DrawableContainer;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class AbstractBitmapDrawable extends DrawableContainer {
+ Rect cursorRect;
+ Rect clipRect;
+
+ AbstractBitmapData data;
+
+ static final Paint _defaultPaint;
+ static final Paint _whitePaint;
+ static final Paint _blackPaint;
+
+ static {
+ _defaultPaint = new Paint();
+ _whitePaint = new Paint();
+ _whitePaint.setColor(0xffffffff);
+ _blackPaint = new Paint();
+ _blackPaint.setColor(0xff000000);
+ }
+
+ AbstractBitmapDrawable(AbstractBitmapData data)
+ {
+ this.data = data;
+ cursorRect = new Rect();
+ clipRect = new Rect();
+ }
+
+ void draw(Canvas canvas, int xoff, int yoff)
+ {
+ canvas.drawBitmap(data.mbitmap, xoff, yoff, _defaultPaint);
+ if(data.vncCanvas.connection.getUseLocalCursor())
+ {
+ setCursorRect(data.vncCanvas.mouseX, data.vncCanvas.mouseY);
+ clipRect.set(cursorRect);
+ if (canvas.clipRect(cursorRect))
+ {
+ drawCursor(canvas);
+ }
+ }
+ }
+
+ void drawCursor(Canvas canvas)
+ {
+ canvas.drawRect(cursorRect,_whitePaint);
+ canvas.drawRect((float)cursorRect.left + 1, (float)cursorRect.top + 1, (float)cursorRect.right - 1, (float)cursorRect.bottom - 1, _blackPaint);
+ }
+
+ void setCursorRect(int mouseX, int mouseY)
+ {
+ cursorRect.left = mouseX - 2;
+ cursorRect.right = cursorRect.left + 4;
+ cursorRect.top = mouseY - 2;
+ cursorRect.bottom = cursorRect.top + 4;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#getIntrinsicHeight()
+ */
+ @Override
+ public int getIntrinsicHeight() {
+ return data.framebufferheight;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#getIntrinsicWidth()
+ */
+ @Override
+ public int getIntrinsicWidth() {
+ return data.framebufferwidth;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#getOpacity()
+ */
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#isStateful()
+ */
+ @Override
+ public boolean isStateful() {
+ return false;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractConnectionBean.java b/app/src/main/java/android/androidVNC/AbstractConnectionBean.java
new file mode 100644
index 000000000..76b5ed1ba
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractConnectionBean.java
@@ -0,0 +1,305 @@
+// This class was generated from android.androidVNC.IConnectionBean by a tool
+// Do not edit this file directly! PLX THX
+package android.androidVNC;
+
+public abstract class AbstractConnectionBean extends com.antlersoft.android.dbimpl.IdImplementationBase implements IConnectionBean {
+
+ public static final String GEN_TABLE_NAME = "CONNECTION_BEAN";
+ public static final int GEN_COUNT = 21;
+
+ // Field constants
+ public static final String GEN_FIELD__ID = "_id";
+ public static final int GEN_ID__ID = 0;
+ public static final String GEN_FIELD_NICKNAME = "NICKNAME";
+ public static final int GEN_ID_NICKNAME = 1;
+ public static final String GEN_FIELD_ADDRESS = "ADDRESS";
+ public static final int GEN_ID_ADDRESS = 2;
+ public static final String GEN_FIELD_PORT = "PORT";
+ public static final int GEN_ID_PORT = 3;
+ public static final String GEN_FIELD_PASSWORD = "PASSWORD";
+ public static final int GEN_ID_PASSWORD = 4;
+ public static final String GEN_FIELD_COLORMODEL = "COLORMODEL";
+ public static final int GEN_ID_COLORMODEL = 5;
+ public static final String GEN_FIELD_FORCEFULL = "FORCEFULL";
+ public static final int GEN_ID_FORCEFULL = 6;
+ public static final String GEN_FIELD_REPEATERID = "REPEATERID";
+ public static final int GEN_ID_REPEATERID = 7;
+ public static final String GEN_FIELD_INPUTMODE = "INPUTMODE";
+ public static final int GEN_ID_INPUTMODE = 8;
+ public static final String GEN_FIELD_SCALEMODE = "SCALEMODE";
+ public static final int GEN_ID_SCALEMODE = 9;
+ public static final String GEN_FIELD_USELOCALCURSOR = "USELOCALCURSOR";
+ public static final int GEN_ID_USELOCALCURSOR = 10;
+ public static final String GEN_FIELD_KEEPPASSWORD = "KEEPPASSWORD";
+ public static final int GEN_ID_KEEPPASSWORD = 11;
+ public static final String GEN_FIELD_FOLLOWMOUSE = "FOLLOWMOUSE";
+ public static final int GEN_ID_FOLLOWMOUSE = 12;
+ public static final String GEN_FIELD_USEREPEATER = "USEREPEATER";
+ public static final int GEN_ID_USEREPEATER = 13;
+ public static final String GEN_FIELD_METALISTID = "METALISTID";
+ public static final int GEN_ID_METALISTID = 14;
+ public static final String GEN_FIELD_LAST_META_KEY_ID = "LAST_META_KEY_ID";
+ public static final int GEN_ID_LAST_META_KEY_ID = 15;
+ public static final String GEN_FIELD_FOLLOWPAN = "FOLLOWPAN";
+ public static final int GEN_ID_FOLLOWPAN = 16;
+ public static final String GEN_FIELD_USERNAME = "USERNAME";
+ public static final int GEN_ID_USERNAME = 17;
+ public static final String GEN_FIELD_SECURECONNECTIONTYPE = "SECURECONNECTIONTYPE";
+ public static final int GEN_ID_SECURECONNECTIONTYPE = 18;
+ public static final String GEN_FIELD_SHOWZOOMBUTTONS = "SHOWZOOMBUTTONS";
+ public static final int GEN_ID_SHOWZOOMBUTTONS = 19;
+ public static final String GEN_FIELD_DOUBLE_TAP_ACTION = "DOUBLE_TAP_ACTION";
+ public static final int GEN_ID_DOUBLE_TAP_ACTION = 20;
+
+ // SQL Command for creating the table
+ public static String GEN_CREATE = "CREATE TABLE CONNECTION_BEAN (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "NICKNAME TEXT," +
+ "ADDRESS TEXT," +
+ "PORT INTEGER," +
+ "PASSWORD TEXT," +
+ "COLORMODEL TEXT," +
+ "FORCEFULL INTEGER," +
+ "REPEATERID TEXT," +
+ "INPUTMODE TEXT," +
+ "SCALEMODE TEXT," +
+ "USELOCALCURSOR INTEGER," +
+ "KEEPPASSWORD INTEGER," +
+ "FOLLOWMOUSE INTEGER," +
+ "USEREPEATER INTEGER," +
+ "METALISTID INTEGER," +
+ "LAST_META_KEY_ID INTEGER," +
+ "FOLLOWPAN INTEGER DEFAULT 0," +
+ "USERNAME TEXT," +
+ "SECURECONNECTIONTYPE TEXT," +
+ "SHOWZOOMBUTTONS INTEGER DEFAULT 1," +
+ "DOUBLE_TAP_ACTION TEXT" +
+ ")";
+
+ // Members corresponding to defined fields
+ private long gen__Id;
+ private java.lang.String gen_nickname;
+ private java.lang.String gen_address;
+ private int gen_port;
+ private java.lang.String gen_password;
+ private java.lang.String gen_colorModel;
+ private long gen_forceFull;
+ private java.lang.String gen_repeaterId;
+ private java.lang.String gen_inputMode;
+ private java.lang.String gen_SCALEMODE;
+ private boolean gen_useLocalCursor;
+ private boolean gen_keepPassword;
+ private boolean gen_followMouse;
+ private boolean gen_useRepeater;
+ private long gen_metaListId;
+ private long gen_LAST_META_KEY_ID;
+ private boolean gen_followPan;
+ private java.lang.String gen_userName;
+ private java.lang.String gen_secureConnectionType;
+ private boolean gen_showZoomButtons;
+ private java.lang.String gen_DOUBLE_TAP_ACTION;
+
+
+ public String Gen_tableName() { return GEN_TABLE_NAME; }
+
+ // Field accessors
+ public long get_Id() { return gen__Id; }
+ public void set_Id(long arg__Id) { gen__Id = arg__Id; }
+ public java.lang.String getNickname() { return gen_nickname; }
+ public void setNickname(java.lang.String arg_nickname) { gen_nickname = arg_nickname; }
+ public java.lang.String getAddress() { return gen_address; }
+ public void setAddress(java.lang.String arg_address) { gen_address = arg_address; }
+ public int getPort() { return gen_port; }
+ public void setPort(int arg_port) { gen_port = arg_port; }
+ public java.lang.String getPassword() { return gen_password; }
+ public void setPassword(java.lang.String arg_password) { gen_password = arg_password; }
+ public java.lang.String getColorModel() { return gen_colorModel; }
+ public void setColorModel(java.lang.String arg_colorModel) { gen_colorModel = arg_colorModel; }
+ public long getForceFull() { return gen_forceFull; }
+ public void setForceFull(long arg_forceFull) { gen_forceFull = arg_forceFull; }
+ public java.lang.String getRepeaterId() { return gen_repeaterId; }
+ public void setRepeaterId(java.lang.String arg_repeaterId) { gen_repeaterId = arg_repeaterId; }
+ public java.lang.String getInputMode() { return gen_inputMode; }
+ public void setInputMode(java.lang.String arg_inputMode) { gen_inputMode = arg_inputMode; }
+ public java.lang.String getScaleModeAsString() { return gen_SCALEMODE; }
+ public void setScaleModeAsString(java.lang.String arg_SCALEMODE) { gen_SCALEMODE = arg_SCALEMODE; }
+ public boolean getUseLocalCursor() { return gen_useLocalCursor; }
+ public void setUseLocalCursor(boolean arg_useLocalCursor) { gen_useLocalCursor = arg_useLocalCursor; }
+ public boolean getKeepPassword() { return gen_keepPassword; }
+ public void setKeepPassword(boolean arg_keepPassword) { gen_keepPassword = arg_keepPassword; }
+ public boolean getFollowMouse() { return gen_followMouse; }
+ public void setFollowMouse(boolean arg_followMouse) { gen_followMouse = arg_followMouse; }
+ public boolean getUseRepeater() { return gen_useRepeater; }
+ public void setUseRepeater(boolean arg_useRepeater) { gen_useRepeater = arg_useRepeater; }
+ public long getMetaListId() { return gen_metaListId; }
+ public void setMetaListId(long arg_metaListId) { gen_metaListId = arg_metaListId; }
+ public long getLastMetaKeyId() { return gen_LAST_META_KEY_ID; }
+ public void setLastMetaKeyId(long arg_LAST_META_KEY_ID) { gen_LAST_META_KEY_ID = arg_LAST_META_KEY_ID; }
+ public boolean getFollowPan() { return gen_followPan; }
+ public void setFollowPan(boolean arg_followPan) { gen_followPan = arg_followPan; }
+ public java.lang.String getUserName() { return gen_userName; }
+ public void setUserName(java.lang.String arg_userName) { gen_userName = arg_userName; }
+ public java.lang.String getSecureConnectionType() { return gen_secureConnectionType; }
+ public void setSecureConnectionType(java.lang.String arg_secureConnectionType) { gen_secureConnectionType = arg_secureConnectionType; }
+ public boolean getShowZoomButtons() { return gen_showZoomButtons; }
+ public void setShowZoomButtons(boolean arg_showZoomButtons) { gen_showZoomButtons = arg_showZoomButtons; }
+ public java.lang.String getDoubleTapActionAsString() { return gen_DOUBLE_TAP_ACTION; }
+ public void setDoubleTapActionAsString(java.lang.String arg_DOUBLE_TAP_ACTION) { gen_DOUBLE_TAP_ACTION = arg_DOUBLE_TAP_ACTION; }
+
+ public android.content.ContentValues Gen_getValues() {
+ android.content.ContentValues values=new android.content.ContentValues();
+ values.put(GEN_FIELD__ID,Long.toString(this.gen__Id));
+ values.put(GEN_FIELD_NICKNAME,this.gen_nickname);
+ values.put(GEN_FIELD_ADDRESS,this.gen_address);
+ values.put(GEN_FIELD_PORT,Integer.toString(this.gen_port));
+ values.put(GEN_FIELD_PASSWORD,this.gen_password);
+ values.put(GEN_FIELD_COLORMODEL,this.gen_colorModel);
+ values.put(GEN_FIELD_FORCEFULL,Long.toString(this.gen_forceFull));
+ values.put(GEN_FIELD_REPEATERID,this.gen_repeaterId);
+ values.put(GEN_FIELD_INPUTMODE,this.gen_inputMode);
+ values.put(GEN_FIELD_SCALEMODE,this.gen_SCALEMODE);
+ values.put(GEN_FIELD_USELOCALCURSOR,(this.gen_useLocalCursor ? "1" : "0"));
+ values.put(GEN_FIELD_KEEPPASSWORD,(this.gen_keepPassword ? "1" : "0"));
+ values.put(GEN_FIELD_FOLLOWMOUSE,(this.gen_followMouse ? "1" : "0"));
+ values.put(GEN_FIELD_USEREPEATER,(this.gen_useRepeater ? "1" : "0"));
+ values.put(GEN_FIELD_METALISTID,Long.toString(this.gen_metaListId));
+ values.put(GEN_FIELD_LAST_META_KEY_ID,Long.toString(this.gen_LAST_META_KEY_ID));
+ values.put(GEN_FIELD_FOLLOWPAN,(this.gen_followPan ? "1" : "0"));
+ values.put(GEN_FIELD_USERNAME,this.gen_userName);
+ values.put(GEN_FIELD_SECURECONNECTIONTYPE,this.gen_secureConnectionType);
+ values.put(GEN_FIELD_SHOWZOOMBUTTONS,(this.gen_showZoomButtons ? "1" : "0"));
+ values.put(GEN_FIELD_DOUBLE_TAP_ACTION,this.gen_DOUBLE_TAP_ACTION);
+ return values;
+ }
+
+ /**
+ * Return an array that gives the column index in the cursor for each field defined
+ * @param cursor Database cursor over some columns, possibly including this table
+ * @return array of column indices; -1 if the column with that id is not in cursor
+ */
+ public int[] Gen_columnIndices(android.database.Cursor cursor) {
+ int[] result=new int[GEN_COUNT];
+ result[0] = cursor.getColumnIndex(GEN_FIELD__ID);
+ // Make compatible with database generated by older version of plugin with uppercase column name
+ if (result[0] == -1) {
+ result[0] = cursor.getColumnIndex("_ID");
+ }
+ result[1] = cursor.getColumnIndex(GEN_FIELD_NICKNAME);
+ result[2] = cursor.getColumnIndex(GEN_FIELD_ADDRESS);
+ result[3] = cursor.getColumnIndex(GEN_FIELD_PORT);
+ result[4] = cursor.getColumnIndex(GEN_FIELD_PASSWORD);
+ result[5] = cursor.getColumnIndex(GEN_FIELD_COLORMODEL);
+ result[6] = cursor.getColumnIndex(GEN_FIELD_FORCEFULL);
+ result[7] = cursor.getColumnIndex(GEN_FIELD_REPEATERID);
+ result[8] = cursor.getColumnIndex(GEN_FIELD_INPUTMODE);
+ result[9] = cursor.getColumnIndex(GEN_FIELD_SCALEMODE);
+ result[10] = cursor.getColumnIndex(GEN_FIELD_USELOCALCURSOR);
+ result[11] = cursor.getColumnIndex(GEN_FIELD_KEEPPASSWORD);
+ result[12] = cursor.getColumnIndex(GEN_FIELD_FOLLOWMOUSE);
+ result[13] = cursor.getColumnIndex(GEN_FIELD_USEREPEATER);
+ result[14] = cursor.getColumnIndex(GEN_FIELD_METALISTID);
+ result[15] = cursor.getColumnIndex(GEN_FIELD_LAST_META_KEY_ID);
+ result[16] = cursor.getColumnIndex(GEN_FIELD_FOLLOWPAN);
+ result[17] = cursor.getColumnIndex(GEN_FIELD_USERNAME);
+ result[18] = cursor.getColumnIndex(GEN_FIELD_SECURECONNECTIONTYPE);
+ result[19] = cursor.getColumnIndex(GEN_FIELD_SHOWZOOMBUTTONS);
+ result[20] = cursor.getColumnIndex(GEN_FIELD_DOUBLE_TAP_ACTION);
+ return result;
+ }
+
+ /**
+ * Populate one instance from a cursor
+ */
+ public void Gen_populate(android.database.Cursor cursor,int[] columnIndices) {
+ if ( columnIndices[GEN_ID__ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID__ID])) {
+ gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
+ }
+ if ( columnIndices[GEN_ID_NICKNAME] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_NICKNAME])) {
+ gen_nickname = cursor.getString(columnIndices[GEN_ID_NICKNAME]);
+ }
+ if ( columnIndices[GEN_ID_ADDRESS] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_ADDRESS])) {
+ gen_address = cursor.getString(columnIndices[GEN_ID_ADDRESS]);
+ }
+ if ( columnIndices[GEN_ID_PORT] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_PORT])) {
+ gen_port = (int)cursor.getInt(columnIndices[GEN_ID_PORT]);
+ }
+ if ( columnIndices[GEN_ID_PASSWORD] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_PASSWORD])) {
+ gen_password = cursor.getString(columnIndices[GEN_ID_PASSWORD]);
+ }
+ if ( columnIndices[GEN_ID_COLORMODEL] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_COLORMODEL])) {
+ gen_colorModel = cursor.getString(columnIndices[GEN_ID_COLORMODEL]);
+ }
+ if ( columnIndices[GEN_ID_FORCEFULL] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_FORCEFULL])) {
+ gen_forceFull = cursor.getLong(columnIndices[GEN_ID_FORCEFULL]);
+ }
+ if ( columnIndices[GEN_ID_REPEATERID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_REPEATERID])) {
+ gen_repeaterId = cursor.getString(columnIndices[GEN_ID_REPEATERID]);
+ }
+ if ( columnIndices[GEN_ID_INPUTMODE] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_INPUTMODE])) {
+ gen_inputMode = cursor.getString(columnIndices[GEN_ID_INPUTMODE]);
+ }
+ if ( columnIndices[GEN_ID_SCALEMODE] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_SCALEMODE])) {
+ gen_SCALEMODE = cursor.getString(columnIndices[GEN_ID_SCALEMODE]);
+ }
+ if ( columnIndices[GEN_ID_USELOCALCURSOR] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_USELOCALCURSOR])) {
+ gen_useLocalCursor = (cursor.getInt(columnIndices[GEN_ID_USELOCALCURSOR]) != 0);
+ }
+ if ( columnIndices[GEN_ID_KEEPPASSWORD] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_KEEPPASSWORD])) {
+ gen_keepPassword = (cursor.getInt(columnIndices[GEN_ID_KEEPPASSWORD]) != 0);
+ }
+ if ( columnIndices[GEN_ID_FOLLOWMOUSE] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_FOLLOWMOUSE])) {
+ gen_followMouse = (cursor.getInt(columnIndices[GEN_ID_FOLLOWMOUSE]) != 0);
+ }
+ if ( columnIndices[GEN_ID_USEREPEATER] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_USEREPEATER])) {
+ gen_useRepeater = (cursor.getInt(columnIndices[GEN_ID_USEREPEATER]) != 0);
+ }
+ if ( columnIndices[GEN_ID_METALISTID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_METALISTID])) {
+ gen_metaListId = cursor.getLong(columnIndices[GEN_ID_METALISTID]);
+ }
+ if ( columnIndices[GEN_ID_LAST_META_KEY_ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_LAST_META_KEY_ID])) {
+ gen_LAST_META_KEY_ID = cursor.getLong(columnIndices[GEN_ID_LAST_META_KEY_ID]);
+ }
+ if ( columnIndices[GEN_ID_FOLLOWPAN] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_FOLLOWPAN])) {
+ gen_followPan = (cursor.getInt(columnIndices[GEN_ID_FOLLOWPAN]) != 0);
+ }
+ if ( columnIndices[GEN_ID_USERNAME] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_USERNAME])) {
+ gen_userName = cursor.getString(columnIndices[GEN_ID_USERNAME]);
+ }
+ if ( columnIndices[GEN_ID_SECURECONNECTIONTYPE] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_SECURECONNECTIONTYPE])) {
+ gen_secureConnectionType = cursor.getString(columnIndices[GEN_ID_SECURECONNECTIONTYPE]);
+ }
+ if ( columnIndices[GEN_ID_SHOWZOOMBUTTONS] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_SHOWZOOMBUTTONS])) {
+ gen_showZoomButtons = (cursor.getInt(columnIndices[GEN_ID_SHOWZOOMBUTTONS]) != 0);
+ }
+ if ( columnIndices[GEN_ID_DOUBLE_TAP_ACTION] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_DOUBLE_TAP_ACTION])) {
+ gen_DOUBLE_TAP_ACTION = cursor.getString(columnIndices[GEN_ID_DOUBLE_TAP_ACTION]);
+ }
+ }
+
+ /**
+ * Populate one instance from a ContentValues
+ */
+ public void Gen_populate(android.content.ContentValues values) {
+ gen__Id = values.getAsLong(GEN_FIELD__ID);
+ gen_nickname = values.getAsString(GEN_FIELD_NICKNAME);
+ gen_address = values.getAsString(GEN_FIELD_ADDRESS);
+ gen_port = (int)values.getAsInteger(GEN_FIELD_PORT);
+ gen_password = values.getAsString(GEN_FIELD_PASSWORD);
+ gen_colorModel = values.getAsString(GEN_FIELD_COLORMODEL);
+ gen_forceFull = values.getAsLong(GEN_FIELD_FORCEFULL);
+ gen_repeaterId = values.getAsString(GEN_FIELD_REPEATERID);
+ gen_inputMode = values.getAsString(GEN_FIELD_INPUTMODE);
+ gen_SCALEMODE = values.getAsString(GEN_FIELD_SCALEMODE);
+ gen_useLocalCursor = (values.getAsInteger(GEN_FIELD_USELOCALCURSOR) != 0);
+ gen_keepPassword = (values.getAsInteger(GEN_FIELD_KEEPPASSWORD) != 0);
+ gen_followMouse = (values.getAsInteger(GEN_FIELD_FOLLOWMOUSE) != 0);
+ gen_useRepeater = (values.getAsInteger(GEN_FIELD_USEREPEATER) != 0);
+ gen_metaListId = values.getAsLong(GEN_FIELD_METALISTID);
+ gen_LAST_META_KEY_ID = values.getAsLong(GEN_FIELD_LAST_META_KEY_ID);
+ gen_followPan = (values.getAsInteger(GEN_FIELD_FOLLOWPAN) != 0);
+ gen_userName = values.getAsString(GEN_FIELD_USERNAME);
+ gen_secureConnectionType = values.getAsString(GEN_FIELD_SECURECONNECTIONTYPE);
+ gen_showZoomButtons = (values.getAsInteger(GEN_FIELD_SHOWZOOMBUTTONS) != 0);
+ gen_DOUBLE_TAP_ACTION = values.getAsString(GEN_FIELD_DOUBLE_TAP_ACTION);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractGestureInputHandler.java b/app/src/main/java/android/androidVNC/AbstractGestureInputHandler.java
new file mode 100644
index 000000000..a00044f6a
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractGestureInputHandler.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.antlersoft.android.bc.BCFactory;
+import com.antlersoft.android.bc.IBCScaleGestureDetector;
+import com.antlersoft.android.bc.OnScaleGestureListener;
+
+/**
+ * An AbstractInputHandler that uses GestureDetector to detect standard gestures in touch events
+ *
+ * @author Michael A. MacDonald
+ */
+abstract class AbstractGestureInputHandler extends GestureDetector.SimpleOnGestureListener implements AbstractInputHandler, OnScaleGestureListener {
+ protected GestureDetector gestures;
+ protected IBCScaleGestureDetector scaleGestures;
+ private VncCanvasActivity activity;
+
+ float xInitialFocus;
+ float yInitialFocus;
+ boolean inScaling;
+
+ private static final String TAG = "AbstractGestureInputHandler";
+
+ AbstractGestureInputHandler(VncCanvasActivity c)
+ {
+ activity = c;
+ gestures=BCFactory.getInstance().getBCGestureDetector().createGestureDetector(c, this);
+ gestures.setOnDoubleTapListener(this);
+ scaleGestures=BCFactory.getInstance().getScaleGestureDetector(c, this);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ scaleGestures.onTouchEvent(evt);
+ return gestures.onTouchEvent(evt);
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.OnScaleGestureListener#onScale(com.antlersoft.android.bc.IBCScaleGestureDetector)
+ */
+ @Override
+ public boolean onScale(IBCScaleGestureDetector detector) {
+ boolean consumed = true;
+ //if (detector.)
+ //Log.i(TAG,"Focus("+detector.getFocusX()+","+detector.getFocusY()+") scaleFactor = "+detector.getScaleFactor());
+ // Calculate focus shift
+ float fx = detector.getFocusX();
+ float fy = detector.getFocusY();
+ double xfs = fx - xInitialFocus;
+ double yfs = fy - yInitialFocus;
+ double fs = Math.sqrt(xfs * xfs + yfs * yfs);
+ if (Math.abs(1.0 - detector.getScaleFactor())<0.02)
+ consumed = false;
+ if (fs * 2< Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan()))
+ {
+ inScaling = true;
+ if (consumed)
+ {
+ //Log.i(TAG,"Adjust scaling "+detector.getScaleFactor());
+ if (activity.vncCanvas != null && activity.vncCanvas.scaling != null)
+ activity.vncCanvas.scaling.adjust(activity, detector.getScaleFactor(), fx, fy);
+ }
+ }
+ return consumed;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.OnScaleGestureListener#onScaleBegin(com.antlersoft.android.bc.IBCScaleGestureDetector)
+ */
+ @Override
+ public boolean onScaleBegin(IBCScaleGestureDetector detector) {
+ xInitialFocus = detector.getFocusX();
+ yInitialFocus = detector.getFocusY();
+ inScaling = false;
+ //Log.i(TAG,"scale begin ("+xInitialFocus+","+yInitialFocus+")");
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.OnScaleGestureListener#onScaleEnd(com.antlersoft.android.bc.IBCScaleGestureDetector)
+ */
+ @Override
+ public void onScaleEnd(IBCScaleGestureDetector detector) {
+ //Log.i(TAG,"scale end");
+ inScaling = false;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractInputHandler.java b/app/src/main/java/android/androidVNC/AbstractInputHandler.java
new file mode 100644
index 000000000..d09302ca3
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractInputHandler.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The VncCanvasActivity has several different ways of handling input from the touchscreen,
+ * keyboard, buttons and trackball. These will be represented by different implementations
+ * of this interface. Putting the different modes in different classes
+ * will keep the logic clean. The relevant Activity callbacks in VncCanvasActivity
+ * are forwarded to methods in AbstractInputHandler.
+ *
+ * It is expected that the implementations will be contained within
+ * VncCanvasActivity, so they can do things like super.VncCanvasActivity.onXXX to invoke
+ * default behavior.
+ * @author Michael A. MacDonald
+ *
+ */
+interface AbstractInputHandler {
+ /**
+ * Note: Menu key code is handled before this is called
+ * @see android.app.Activity#onKeyDown(int keyCode, KeyEvent evt)
+ */
+ boolean onKeyDown(int keyCode, KeyEvent evt);
+ /**
+ * Note: Menu key code is handled before this is called
+ * @see android.app.Activity#onKeyUp(int keyCode, KeyEvent evt)
+ */
+ boolean onKeyUp(int keyCode, KeyEvent evt);
+ /* (non-Javadoc)
+ * @see android.app.Activity#onTrackballEvent(android.view.MotionEvent)
+ */
+ boolean onTrackballEvent( MotionEvent evt);
+ /* (non-Javadoc)
+ * @see android.app.Activity#onTrackballEvent(android.view.MotionEvent)
+ */
+ boolean onTouchEvent( MotionEvent evt);
+
+ /**
+ * Return a user-friendly description for this mode; it will be displayed in a toaster
+ * when changing modes.
+ * @return
+ */
+ CharSequence getHandlerDescription();
+
+ /**
+ * Return an internal name for this handler; this name will be stable across language
+ * and version changes
+ */
+ String getName();
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractMetaKeyBean.java b/app/src/main/java/android/androidVNC/AbstractMetaKeyBean.java
new file mode 100644
index 000000000..b70fd4835
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractMetaKeyBean.java
@@ -0,0 +1,149 @@
+// This class was generated from android.androidVNC.IMetaKey by a tool
+// Do not edit this file directly! PLX THX
+package android.androidVNC;
+
+public abstract class AbstractMetaKeyBean extends com.antlersoft.android.dbimpl.IdImplementationBase implements IMetaKey {
+
+ public static final String GEN_TABLE_NAME = "META_KEY";
+ public static final int GEN_COUNT = 8;
+
+ // Field constants
+ public static final String GEN_FIELD__ID = "_id";
+ public static final int GEN_ID__ID = 0;
+ public static final String GEN_FIELD_METALISTID = "METALISTID";
+ public static final int GEN_ID_METALISTID = 1;
+ public static final String GEN_FIELD_KEYDESC = "KEYDESC";
+ public static final int GEN_ID_KEYDESC = 2;
+ public static final String GEN_FIELD_METAFLAGS = "METAFLAGS";
+ public static final int GEN_ID_METAFLAGS = 3;
+ public static final String GEN_FIELD_MOUSECLICK = "MOUSECLICK";
+ public static final int GEN_ID_MOUSECLICK = 4;
+ public static final String GEN_FIELD_MOUSEBUTTONS = "MOUSEBUTTONS";
+ public static final int GEN_ID_MOUSEBUTTONS = 5;
+ public static final String GEN_FIELD_KEYSYM = "KEYSYM";
+ public static final int GEN_ID_KEYSYM = 6;
+ public static final String GEN_FIELD_SHORTCUT = "SHORTCUT";
+ public static final int GEN_ID_SHORTCUT = 7;
+
+ // SQL Command for creating the table
+ public static String GEN_CREATE = "CREATE TABLE META_KEY (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "METALISTID INTEGER," +
+ "KEYDESC TEXT," +
+ "METAFLAGS INTEGER," +
+ "MOUSECLICK INTEGER," +
+ "MOUSEBUTTONS INTEGER," +
+ "KEYSYM INTEGER," +
+ "SHORTCUT TEXT" +
+ ")";
+
+ // Members corresponding to defined fields
+ private long gen__Id;
+ private long gen_metaListId;
+ private java.lang.String gen_keyDesc;
+ private int gen_metaFlags;
+ private boolean gen_mouseClick;
+ private int gen_mouseButtons;
+ private int gen_keySym;
+ private java.lang.String gen_shortcut;
+
+
+ public String Gen_tableName() { return GEN_TABLE_NAME; }
+
+ // Field accessors
+ public long get_Id() { return gen__Id; }
+ public void set_Id(long arg__Id) { gen__Id = arg__Id; }
+ public long getMetaListId() { return gen_metaListId; }
+ public void setMetaListId(long arg_metaListId) { gen_metaListId = arg_metaListId; }
+ public java.lang.String getKeyDesc() { return gen_keyDesc; }
+ public void setKeyDesc(java.lang.String arg_keyDesc) { gen_keyDesc = arg_keyDesc; }
+ public int getMetaFlags() { return gen_metaFlags; }
+ public void setMetaFlags(int arg_metaFlags) { gen_metaFlags = arg_metaFlags; }
+ public boolean isMouseClick() { return gen_mouseClick; }
+ public void setMouseClick(boolean arg_mouseClick) { gen_mouseClick = arg_mouseClick; }
+ public int getMouseButtons() { return gen_mouseButtons; }
+ public void setMouseButtons(int arg_mouseButtons) { gen_mouseButtons = arg_mouseButtons; }
+ public int getKeySym() { return gen_keySym; }
+ public void setKeySym(int arg_keySym) { gen_keySym = arg_keySym; }
+ public java.lang.String getShortcut() { return gen_shortcut; }
+ public void setShortcut(java.lang.String arg_shortcut) { gen_shortcut = arg_shortcut; }
+
+ public android.content.ContentValues Gen_getValues() {
+ android.content.ContentValues values=new android.content.ContentValues();
+ values.put(GEN_FIELD__ID,Long.toString(this.gen__Id));
+ values.put(GEN_FIELD_METALISTID,Long.toString(this.gen_metaListId));
+ values.put(GEN_FIELD_KEYDESC,this.gen_keyDesc);
+ values.put(GEN_FIELD_METAFLAGS,Integer.toString(this.gen_metaFlags));
+ values.put(GEN_FIELD_MOUSECLICK,(this.gen_mouseClick ? "1" : "0"));
+ values.put(GEN_FIELD_MOUSEBUTTONS,Integer.toString(this.gen_mouseButtons));
+ values.put(GEN_FIELD_KEYSYM,Integer.toString(this.gen_keySym));
+ values.put(GEN_FIELD_SHORTCUT,this.gen_shortcut);
+ return values;
+ }
+
+ /**
+ * Return an array that gives the column index in the cursor for each field defined
+ * @param cursor Database cursor over some columns, possibly including this table
+ * @return array of column indices; -1 if the column with that id is not in cursor
+ */
+ public int[] Gen_columnIndices(android.database.Cursor cursor) {
+ int[] result=new int[GEN_COUNT];
+ result[0] = cursor.getColumnIndex(GEN_FIELD__ID);
+ // Make compatible with database generated by older version of plugin with uppercase column name
+ if (result[0] == -1) {
+ result[0] = cursor.getColumnIndex("_ID");
+ }
+ result[1] = cursor.getColumnIndex(GEN_FIELD_METALISTID);
+ result[2] = cursor.getColumnIndex(GEN_FIELD_KEYDESC);
+ result[3] = cursor.getColumnIndex(GEN_FIELD_METAFLAGS);
+ result[4] = cursor.getColumnIndex(GEN_FIELD_MOUSECLICK);
+ result[5] = cursor.getColumnIndex(GEN_FIELD_MOUSEBUTTONS);
+ result[6] = cursor.getColumnIndex(GEN_FIELD_KEYSYM);
+ result[7] = cursor.getColumnIndex(GEN_FIELD_SHORTCUT);
+ return result;
+ }
+
+ /**
+ * Populate one instance from a cursor
+ */
+ public void Gen_populate(android.database.Cursor cursor,int[] columnIndices) {
+ if ( columnIndices[GEN_ID__ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID__ID])) {
+ gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
+ }
+ if ( columnIndices[GEN_ID_METALISTID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_METALISTID])) {
+ gen_metaListId = cursor.getLong(columnIndices[GEN_ID_METALISTID]);
+ }
+ if ( columnIndices[GEN_ID_KEYDESC] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_KEYDESC])) {
+ gen_keyDesc = cursor.getString(columnIndices[GEN_ID_KEYDESC]);
+ }
+ if ( columnIndices[GEN_ID_METAFLAGS] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_METAFLAGS])) {
+ gen_metaFlags = (int)cursor.getInt(columnIndices[GEN_ID_METAFLAGS]);
+ }
+ if ( columnIndices[GEN_ID_MOUSECLICK] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_MOUSECLICK])) {
+ gen_mouseClick = (cursor.getInt(columnIndices[GEN_ID_MOUSECLICK]) != 0);
+ }
+ if ( columnIndices[GEN_ID_MOUSEBUTTONS] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_MOUSEBUTTONS])) {
+ gen_mouseButtons = (int)cursor.getInt(columnIndices[GEN_ID_MOUSEBUTTONS]);
+ }
+ if ( columnIndices[GEN_ID_KEYSYM] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_KEYSYM])) {
+ gen_keySym = (int)cursor.getInt(columnIndices[GEN_ID_KEYSYM]);
+ }
+ if ( columnIndices[GEN_ID_SHORTCUT] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_SHORTCUT])) {
+ gen_shortcut = cursor.getString(columnIndices[GEN_ID_SHORTCUT]);
+ }
+ }
+
+ /**
+ * Populate one instance from a ContentValues
+ */
+ public void Gen_populate(android.content.ContentValues values) {
+ gen__Id = values.getAsLong(GEN_FIELD__ID);
+ gen_metaListId = values.getAsLong(GEN_FIELD_METALISTID);
+ gen_keyDesc = values.getAsString(GEN_FIELD_KEYDESC);
+ gen_metaFlags = (int)values.getAsInteger(GEN_FIELD_METAFLAGS);
+ gen_mouseClick = (values.getAsInteger(GEN_FIELD_MOUSECLICK) != 0);
+ gen_mouseButtons = (int)values.getAsInteger(GEN_FIELD_MOUSEBUTTONS);
+ gen_keySym = (int)values.getAsInteger(GEN_FIELD_KEYSYM);
+ gen_shortcut = values.getAsString(GEN_FIELD_SHORTCUT);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/AbstractScaling.java b/app/src/main/java/android/androidVNC/AbstractScaling.java
new file mode 100644
index 000000000..41476e3a5
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/AbstractScaling.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.widget.*;
+import net.kdt.pojavlaunch.*;
+
+
+/**
+ * @author Michael A. MacDonald
+ *
+ * A scaling mode for the VncCanvas; based on ImageView.ScaleType
+ */
+abstract class AbstractScaling {
+ private static final int scaleModeIds[] = { R.id.itemFitToScreen, R.id.itemOneToOne, R.id.itemZoomable };
+
+ private static AbstractScaling[] scalings;
+
+ static AbstractScaling getById(int id)
+ {
+ if ( scalings==null)
+ {
+ scalings=new AbstractScaling[scaleModeIds.length];
+ }
+ for ( int i=0; i(64, (float)0.25);
+ orderedList = new Vector(32, 8);
+ }
+
+ public void add(CapabilityInfo capinfo) {
+ Integer key = new Integer(capinfo.getCode());
+ infoMap.put(key, capinfo);
+ }
+
+ public void add(int code, String vendor, String name, String desc) {
+ Integer key = new Integer(code);
+ infoMap.put(key, new CapabilityInfo(code, vendor, name, desc));
+ }
+
+ public boolean isKnown(int code) {
+ return infoMap.containsKey(new Integer(code));
+ }
+
+ public CapabilityInfo getInfo(int code) {
+ return (CapabilityInfo)infoMap.get(new Integer(code));
+ }
+
+ public String getDescription(int code) {
+ CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+ if (capinfo == null)
+ return null;
+
+ return capinfo.getDescription();
+ }
+
+ public boolean enable(CapabilityInfo other) {
+ Integer key = new Integer(other.getCode());
+ CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(key);
+ if (capinfo == null)
+ return false;
+
+ boolean enabled = capinfo.enableIfEquals(other);
+ if (enabled)
+ orderedList.addElement(key);
+
+ return enabled;
+ }
+
+ public boolean isEnabled(int code) {
+ CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+ if (capinfo == null)
+ return false;
+
+ return capinfo.isEnabled();
+ }
+
+ public int numEnabled() {
+ return orderedList.size();
+ }
+
+ public int getByOrder(int idx) {
+ int code;
+ try {
+ code = ((Integer)orderedList.elementAt(idx)).intValue();
+ } catch (ArrayIndexOutOfBoundsException e) {
+ code = 0;
+ }
+ return code;
+ }
+
+ // Protected data
+
+ protected Hashtable infoMap;
+ protected Vector orderedList;
+}
+
diff --git a/app/src/main/java/android/androidVNC/ColorModel256.java b/app/src/main/java/android/androidVNC/ColorModel256.java
new file mode 100644
index 000000000..372139a27
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ColorModel256.java
@@ -0,0 +1,266 @@
+package android.androidVNC;
+
+public class ColorModel256 {
+
+ public final static int [] colors;
+
+ static {
+ colors = new int[256];
+ colors[0]=0xff000000;
+ colors[1]=0xff240000;
+ colors[2]=0xff490000;
+ colors[3]=0xff6d0000;
+ colors[4]=0xff920000;
+ colors[5]=0xffb60000;
+ colors[6]=0xffdb0000;
+ colors[7]=0xffff0000;
+ colors[8]=0xff002400;
+ colors[9]=0xff242400;
+ colors[10]=0xff492400;
+ colors[11]=0xff6d2400;
+ colors[12]=0xff922400;
+ colors[13]=0xffb62400;
+ colors[14]=0xffdb2400;
+ colors[15]=0xffff2400;
+ colors[16]=0xff004900;
+ colors[17]=0xff244900;
+ colors[18]=0xff494900;
+ colors[19]=0xff6d4900;
+ colors[20]=0xff924900;
+ colors[21]=0xffb64900;
+ colors[22]=0xffdb4900;
+ colors[23]=0xffff4900;
+ colors[24]=0xff006d00;
+ colors[25]=0xff246d00;
+ colors[26]=0xff496d00;
+ colors[27]=0xff6d6d00;
+ colors[28]=0xff926d00;
+ colors[29]=0xffb66d00;
+ colors[30]=0xffdb6d00;
+ colors[31]=0xffff6d00;
+ colors[32]=0xff009200;
+ colors[33]=0xff249200;
+ colors[34]=0xff499200;
+ colors[35]=0xff6d9200;
+ colors[36]=0xff929200;
+ colors[37]=0xffb69200;
+ colors[38]=0xffdb9200;
+ colors[39]=0xffff9200;
+ colors[40]=0xff00b600;
+ colors[41]=0xff24b600;
+ colors[42]=0xff49b600;
+ colors[43]=0xff6db600;
+ colors[44]=0xff92b600;
+ colors[45]=0xffb6b600;
+ colors[46]=0xffdbb600;
+ colors[47]=0xffffb600;
+ colors[48]=0xff00db00;
+ colors[49]=0xff24db00;
+ colors[50]=0xff49db00;
+ colors[51]=0xff6ddb00;
+ colors[52]=0xff92db00;
+ colors[53]=0xffb6db00;
+ colors[54]=0xffdbdb00;
+ colors[55]=0xffffdb00;
+ colors[56]=0xff00ff00;
+ colors[57]=0xff24ff00;
+ colors[58]=0xff49ff00;
+ colors[59]=0xff6dff00;
+ colors[60]=0xff92ff00;
+ colors[61]=0xffb6ff00;
+ colors[62]=0xffdbff00;
+ colors[63]=0xffffff00;
+ colors[64]=0xff000055;
+ colors[65]=0xff240055;
+ colors[66]=0xff490055;
+ colors[67]=0xff6d0055;
+ colors[68]=0xff920055;
+ colors[69]=0xffb60055;
+ colors[70]=0xffdb0055;
+ colors[71]=0xffff0055;
+ colors[72]=0xff002455;
+ colors[73]=0xff242455;
+ colors[74]=0xff492455;
+ colors[75]=0xff6d2455;
+ colors[76]=0xff922455;
+ colors[77]=0xffb62455;
+ colors[78]=0xffdb2455;
+ colors[79]=0xffff2455;
+ colors[80]=0xff004955;
+ colors[81]=0xff244955;
+ colors[82]=0xff494955;
+ colors[83]=0xff6d4955;
+ colors[84]=0xff924955;
+ colors[85]=0xffb64955;
+ colors[86]=0xffdb4955;
+ colors[87]=0xffff4955;
+ colors[88]=0xff006d55;
+ colors[89]=0xff246d55;
+ colors[90]=0xff496d55;
+ colors[91]=0xff6d6d55;
+ colors[92]=0xff926d55;
+ colors[93]=0xffb66d55;
+ colors[94]=0xffdb6d55;
+ colors[95]=0xffff6d55;
+ colors[96]=0xff009255;
+ colors[97]=0xff249255;
+ colors[98]=0xff499255;
+ colors[99]=0xff6d9255;
+ colors[100]=0xff929255;
+ colors[101]=0xffb69255;
+ colors[102]=0xffdb9255;
+ colors[103]=0xffff9255;
+ colors[104]=0xff00b655;
+ colors[105]=0xff24b655;
+ colors[106]=0xff49b655;
+ colors[107]=0xff6db655;
+ colors[108]=0xff92b655;
+ colors[109]=0xffb6b655;
+ colors[110]=0xffdbb655;
+ colors[111]=0xffffb655;
+ colors[112]=0xff00db55;
+ colors[113]=0xff24db55;
+ colors[114]=0xff49db55;
+ colors[115]=0xff6ddb55;
+ colors[116]=0xff92db55;
+ colors[117]=0xffb6db55;
+ colors[118]=0xffdbdb55;
+ colors[119]=0xffffdb55;
+ colors[120]=0xff00ff55;
+ colors[121]=0xff24ff55;
+ colors[122]=0xff49ff55;
+ colors[123]=0xff6dff55;
+ colors[124]=0xff92ff55;
+ colors[125]=0xffb6ff55;
+ colors[126]=0xffdbff55;
+ colors[127]=0xffffff55;
+ colors[128]=0xff0000aa;
+ colors[129]=0xff2400aa;
+ colors[130]=0xff4900aa;
+ colors[131]=0xff6d00aa;
+ colors[132]=0xff9200aa;
+ colors[133]=0xffb600aa;
+ colors[134]=0xffdb00aa;
+ colors[135]=0xffff00aa;
+ colors[136]=0xff0024aa;
+ colors[137]=0xff2424aa;
+ colors[138]=0xff4924aa;
+ colors[139]=0xff6d24aa;
+ colors[140]=0xff9224aa;
+ colors[141]=0xffb624aa;
+ colors[142]=0xffdb24aa;
+ colors[143]=0xffff24aa;
+ colors[144]=0xff0049aa;
+ colors[145]=0xff2449aa;
+ colors[146]=0xff4949aa;
+ colors[147]=0xff6d49aa;
+ colors[148]=0xff9249aa;
+ colors[149]=0xffb649aa;
+ colors[150]=0xffdb49aa;
+ colors[151]=0xffff49aa;
+ colors[152]=0xff006daa;
+ colors[153]=0xff246daa;
+ colors[154]=0xff496daa;
+ colors[155]=0xff6d6daa;
+ colors[156]=0xff926daa;
+ colors[157]=0xffb66daa;
+ colors[158]=0xffdb6daa;
+ colors[159]=0xffff6daa;
+ colors[160]=0xff0092aa;
+ colors[161]=0xff2492aa;
+ colors[162]=0xff4992aa;
+ colors[163]=0xff6d92aa;
+ colors[164]=0xff9292aa;
+ colors[165]=0xffb692aa;
+ colors[166]=0xffdb92aa;
+ colors[167]=0xffff92aa;
+ colors[168]=0xff00b6aa;
+ colors[169]=0xff24b6aa;
+ colors[170]=0xff49b6aa;
+ colors[171]=0xff6db6aa;
+ colors[172]=0xff92b6aa;
+ colors[173]=0xffb6b6aa;
+ colors[174]=0xffdbb6aa;
+ colors[175]=0xffffb6aa;
+ colors[176]=0xff00dbaa;
+ colors[177]=0xff24dbaa;
+ colors[178]=0xff49dbaa;
+ colors[179]=0xff6ddbaa;
+ colors[180]=0xff92dbaa;
+ colors[181]=0xffb6dbaa;
+ colors[182]=0xffdbdbaa;
+ colors[183]=0xffffdbaa;
+ colors[184]=0xff00ffaa;
+ colors[185]=0xff24ffaa;
+ colors[186]=0xff49ffaa;
+ colors[187]=0xff6dffaa;
+ colors[188]=0xff92ffaa;
+ colors[189]=0xffb6ffaa;
+ colors[190]=0xffdbffaa;
+ colors[191]=0xffffffaa;
+ colors[192]=0xff0000ff;
+ colors[193]=0xff2400ff;
+ colors[194]=0xff4900ff;
+ colors[195]=0xff6d00ff;
+ colors[196]=0xff9200ff;
+ colors[197]=0xffb600ff;
+ colors[198]=0xffdb00ff;
+ colors[199]=0xffff00ff;
+ colors[200]=0xff0024ff;
+ colors[201]=0xff2424ff;
+ colors[202]=0xff4924ff;
+ colors[203]=0xff6d24ff;
+ colors[204]=0xff9224ff;
+ colors[205]=0xffb624ff;
+ colors[206]=0xffdb24ff;
+ colors[207]=0xffff24ff;
+ colors[208]=0xff0049ff;
+ colors[209]=0xff2449ff;
+ colors[210]=0xff4949ff;
+ colors[211]=0xff6d49ff;
+ colors[212]=0xff9249ff;
+ colors[213]=0xffb649ff;
+ colors[214]=0xffdb49ff;
+ colors[215]=0xffff49ff;
+ colors[216]=0xff006dff;
+ colors[217]=0xff246dff;
+ colors[218]=0xff496dff;
+ colors[219]=0xff6d6dff;
+ colors[220]=0xff926dff;
+ colors[221]=0xffb66dff;
+ colors[222]=0xffdb6dff;
+ colors[223]=0xffff6dff;
+ colors[224]=0xff0092ff;
+ colors[225]=0xff2492ff;
+ colors[226]=0xff4992ff;
+ colors[227]=0xff6d92ff;
+ colors[228]=0xff9292ff;
+ colors[229]=0xffb692ff;
+ colors[230]=0xffdb92ff;
+ colors[231]=0xffff92ff;
+ colors[232]=0xff00b6ff;
+ colors[233]=0xff24b6ff;
+ colors[234]=0xff49b6ff;
+ colors[235]=0xff6db6ff;
+ colors[236]=0xff92b6ff;
+ colors[237]=0xffb6b6ff;
+ colors[238]=0xffdbb6ff;
+ colors[239]=0xffffb6ff;
+ colors[240]=0xff00dbff;
+ colors[241]=0xff24dbff;
+ colors[242]=0xff49dbff;
+ colors[243]=0xff6ddbff;
+ colors[244]=0xff92dbff;
+ colors[245]=0xffb6dbff;
+ colors[246]=0xffdbdbff;
+ colors[247]=0xffffdbff;
+ colors[248]=0xff00ffff;
+ colors[249]=0xff24ffff;
+ colors[250]=0xff49ffff;
+ colors[251]=0xff6dffff;
+ colors[252]=0xff92ffff;
+ colors[253]=0xffb6ffff;
+ colors[254]=0xffdbffff;
+ colors[255]=0xffffffff;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ColorModel64.java b/app/src/main/java/android/androidVNC/ColorModel64.java
new file mode 100644
index 000000000..916649499
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ColorModel64.java
@@ -0,0 +1,267 @@
+package android.androidVNC;
+
+public class ColorModel64 {
+
+ public final static int [] colors;
+
+ static {
+ colors = new int[256];
+ colors[0]=0xff000000;
+ colors[1]=0xff000055;
+ colors[2]=0xff0000aa;
+ colors[3]=0xff0000ff;
+ colors[4]=0xff005500;
+ colors[5]=0xff005555;
+ colors[6]=0xff0055aa;
+ colors[7]=0xff0055ff;
+ colors[8]=0xff00aa00;
+ colors[9]=0xff00aa55;
+ colors[10]=0xff00aaaa;
+ colors[11]=0xff00aaff;
+ colors[12]=0xff00ff00;
+ colors[13]=0xff00ff55;
+ colors[14]=0xff00ffaa;
+ colors[15]=0xff00ffff;
+ colors[16]=0xff550000;
+ colors[17]=0xff550055;
+ colors[18]=0xff5500aa;
+ colors[19]=0xff5500ff;
+ colors[20]=0xff555500;
+ colors[21]=0xff555555;
+ colors[22]=0xff5555aa;
+ colors[23]=0xff5555ff;
+ colors[24]=0xff55aa00;
+ colors[25]=0xff55aa55;
+ colors[26]=0xff55aaaa;
+ colors[27]=0xff55aaff;
+ colors[28]=0xff55ff00;
+ colors[29]=0xff55ff55;
+ colors[30]=0xff55ffaa;
+ colors[31]=0xff55ffff;
+ colors[32]=0xffaa0000;
+ colors[33]=0xffaa0055;
+ colors[34]=0xffaa00aa;
+ colors[35]=0xffaa00ff;
+ colors[36]=0xffaa5500;
+ colors[37]=0xffaa5555;
+ colors[38]=0xffaa55aa;
+ colors[39]=0xffaa55ff;
+ colors[40]=0xffaaaa00;
+ colors[41]=0xffaaaa55;
+ colors[42]=0xffaaaaaa;
+ colors[43]=0xffaaaaff;
+ colors[44]=0xffaaff00;
+ colors[45]=0xffaaff55;
+ colors[46]=0xffaaffaa;
+ colors[47]=0xffaaffff;
+ colors[48]=0xffff0000;
+ colors[49]=0xffff0055;
+ colors[50]=0xffff00aa;
+ colors[51]=0xffff00ff;
+ colors[52]=0xffff5500;
+ colors[53]=0xffff5555;
+ colors[54]=0xffff55aa;
+ colors[55]=0xffff55ff;
+ colors[56]=0xffffaa00;
+ colors[57]=0xffffaa55;
+ colors[58]=0xffffaaaa;
+ colors[59]=0xffffaaff;
+ colors[60]=0xffffff00;
+ colors[61]=0xffffff55;
+ colors[62]=0xffffffaa;
+ colors[63]=0xffffffff;
+ colors[64]=0xff000000;
+ colors[65]=0xff000055;
+ colors[66]=0xff0000aa;
+ colors[67]=0xff0000ff;
+ colors[68]=0xff005500;
+ colors[69]=0xff005555;
+ colors[70]=0xff0055aa;
+ colors[71]=0xff0055ff;
+ colors[72]=0xff00aa00;
+ colors[73]=0xff00aa55;
+ colors[74]=0xff00aaaa;
+ colors[75]=0xff00aaff;
+ colors[76]=0xff00ff00;
+ colors[77]=0xff00ff55;
+ colors[78]=0xff00ffaa;
+ colors[79]=0xff00ffff;
+ colors[80]=0xff550000;
+ colors[81]=0xff550055;
+ colors[82]=0xff5500aa;
+ colors[83]=0xff5500ff;
+ colors[84]=0xff555500;
+ colors[85]=0xff555555;
+ colors[86]=0xff5555aa;
+ colors[87]=0xff5555ff;
+ colors[88]=0xff55aa00;
+ colors[89]=0xff55aa55;
+ colors[90]=0xff55aaaa;
+ colors[91]=0xff55aaff;
+ colors[92]=0xff55ff00;
+ colors[93]=0xff55ff55;
+ colors[94]=0xff55ffaa;
+ colors[95]=0xff55ffff;
+ colors[96]=0xffaa0000;
+ colors[97]=0xffaa0055;
+ colors[98]=0xffaa00aa;
+ colors[99]=0xffaa00ff;
+ colors[100]=0xffaa5500;
+ colors[101]=0xffaa5555;
+ colors[102]=0xffaa55aa;
+ colors[103]=0xffaa55ff;
+ colors[104]=0xffaaaa00;
+ colors[105]=0xffaaaa55;
+ colors[106]=0xffaaaaaa;
+ colors[107]=0xffaaaaff;
+ colors[108]=0xffaaff00;
+ colors[109]=0xffaaff55;
+ colors[110]=0xffaaffaa;
+ colors[111]=0xffaaffff;
+ colors[112]=0xffff0000;
+ colors[113]=0xffff0055;
+ colors[114]=0xffff00aa;
+ colors[115]=0xffff00ff;
+ colors[116]=0xffff5500;
+ colors[117]=0xffff5555;
+ colors[118]=0xffff55aa;
+ colors[119]=0xffff55ff;
+ colors[120]=0xffffaa00;
+ colors[121]=0xffffaa55;
+ colors[122]=0xffffaaaa;
+ colors[123]=0xffffaaff;
+ colors[124]=0xffffff00;
+ colors[125]=0xffffff55;
+ colors[126]=0xffffffaa;
+ colors[127]=0xffffffff;
+ colors[128]=0xff000000;
+ colors[129]=0xff000055;
+ colors[130]=0xff0000aa;
+ colors[131]=0xff0000ff;
+ colors[132]=0xff005500;
+ colors[133]=0xff005555;
+ colors[134]=0xff0055aa;
+ colors[135]=0xff0055ff;
+ colors[136]=0xff00aa00;
+ colors[137]=0xff00aa55;
+ colors[138]=0xff00aaaa;
+ colors[139]=0xff00aaff;
+ colors[140]=0xff00ff00;
+ colors[141]=0xff00ff55;
+ colors[142]=0xff00ffaa;
+ colors[143]=0xff00ffff;
+ colors[144]=0xff550000;
+ colors[145]=0xff550055;
+ colors[146]=0xff5500aa;
+ colors[147]=0xff5500ff;
+ colors[148]=0xff555500;
+ colors[149]=0xff555555;
+ colors[150]=0xff5555aa;
+ colors[151]=0xff5555ff;
+ colors[152]=0xff55aa00;
+ colors[153]=0xff55aa55;
+ colors[154]=0xff55aaaa;
+ colors[155]=0xff55aaff;
+ colors[156]=0xff55ff00;
+ colors[157]=0xff55ff55;
+ colors[158]=0xff55ffaa;
+ colors[159]=0xff55ffff;
+ colors[160]=0xffaa0000;
+ colors[161]=0xffaa0055;
+ colors[162]=0xffaa00aa;
+ colors[163]=0xffaa00ff;
+ colors[164]=0xffaa5500;
+ colors[165]=0xffaa5555;
+ colors[166]=0xffaa55aa;
+ colors[167]=0xffaa55ff;
+ colors[168]=0xffaaaa00;
+ colors[169]=0xffaaaa55;
+ colors[170]=0xffaaaaaa;
+ colors[171]=0xffaaaaff;
+ colors[172]=0xffaaff00;
+ colors[173]=0xffaaff55;
+ colors[174]=0xffaaffaa;
+ colors[175]=0xffaaffff;
+ colors[176]=0xffff0000;
+ colors[177]=0xffff0055;
+ colors[178]=0xffff00aa;
+ colors[179]=0xffff00ff;
+ colors[180]=0xffff5500;
+ colors[181]=0xffff5555;
+ colors[182]=0xffff55aa;
+ colors[183]=0xffff55ff;
+ colors[184]=0xffffaa00;
+ colors[185]=0xffffaa55;
+ colors[186]=0xffffaaaa;
+ colors[187]=0xffffaaff;
+ colors[188]=0xffffff00;
+ colors[189]=0xffffff55;
+ colors[190]=0xffffffaa;
+ colors[191]=0xffffffff;
+ colors[192]=0xff000000;
+ colors[193]=0xff000055;
+ colors[194]=0xff0000aa;
+ colors[195]=0xff0000ff;
+ colors[196]=0xff005500;
+ colors[197]=0xff005555;
+ colors[198]=0xff0055aa;
+ colors[199]=0xff0055ff;
+ colors[200]=0xff00aa00;
+ colors[201]=0xff00aa55;
+ colors[202]=0xff00aaaa;
+ colors[203]=0xff00aaff;
+ colors[204]=0xff00ff00;
+ colors[205]=0xff00ff55;
+ colors[206]=0xff00ffaa;
+ colors[207]=0xff00ffff;
+ colors[208]=0xff550000;
+ colors[209]=0xff550055;
+ colors[210]=0xff5500aa;
+ colors[211]=0xff5500ff;
+ colors[212]=0xff555500;
+ colors[213]=0xff555555;
+ colors[214]=0xff5555aa;
+ colors[215]=0xff5555ff;
+ colors[216]=0xff55aa00;
+ colors[217]=0xff55aa55;
+ colors[218]=0xff55aaaa;
+ colors[219]=0xff55aaff;
+ colors[220]=0xff55ff00;
+ colors[221]=0xff55ff55;
+ colors[222]=0xff55ffaa;
+ colors[223]=0xff55ffff;
+ colors[224]=0xffaa0000;
+ colors[225]=0xffaa0055;
+ colors[226]=0xffaa00aa;
+ colors[227]=0xffaa00ff;
+ colors[228]=0xffaa5500;
+ colors[229]=0xffaa5555;
+ colors[230]=0xffaa55aa;
+ colors[231]=0xffaa55ff;
+ colors[232]=0xffaaaa00;
+ colors[233]=0xffaaaa55;
+ colors[234]=0xffaaaaaa;
+ colors[235]=0xffaaaaff;
+ colors[236]=0xffaaff00;
+ colors[237]=0xffaaff55;
+ colors[238]=0xffaaffaa;
+ colors[239]=0xffaaffff;
+ colors[240]=0xffff0000;
+ colors[241]=0xffff0055;
+ colors[242]=0xffff00aa;
+ colors[243]=0xffff00ff;
+ colors[244]=0xffff5500;
+ colors[245]=0xffff5555;
+ colors[246]=0xffff55aa;
+ colors[247]=0xffff55ff;
+ colors[248]=0xffffaa00;
+ colors[249]=0xffffaa55;
+ colors[250]=0xffffaaaa;
+ colors[251]=0xffffaaff;
+ colors[252]=0xffffff00;
+ colors[253]=0xffffff55;
+ colors[254]=0xffffffaa;
+ colors[255]=0xffffffff;
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ColorModel8.java b/app/src/main/java/android/androidVNC/ColorModel8.java
new file mode 100644
index 000000000..c767822cf
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ColorModel8.java
@@ -0,0 +1,266 @@
+package android.androidVNC;
+
+public class ColorModel8 {
+
+ public final static int [] colors;
+
+ static {
+ colors = new int[256];
+ colors[0]=0xff000000;
+ colors[1]=0xff0000ff;
+ colors[2]=0xff00ff00;
+ colors[3]=0xff00ffff;
+ colors[4]=0xffff0000;
+ colors[5]=0xffff00ff;
+ colors[6]=0xffffff00;
+ colors[7]=0xffffffff;
+ colors[8]=0xff000000;
+ colors[9]=0xff0000ff;
+ colors[10]=0xff00ff00;
+ colors[11]=0xff00ffff;
+ colors[12]=0xffff0000;
+ colors[13]=0xffff00ff;
+ colors[14]=0xffffff00;
+ colors[15]=0xffffffff;
+ colors[16]=0xff000000;
+ colors[17]=0xff0000ff;
+ colors[18]=0xff00ff00;
+ colors[19]=0xff00ffff;
+ colors[20]=0xffff0000;
+ colors[21]=0xffff00ff;
+ colors[22]=0xffffff00;
+ colors[23]=0xffffffff;
+ colors[24]=0xff000000;
+ colors[25]=0xff0000ff;
+ colors[26]=0xff00ff00;
+ colors[27]=0xff00ffff;
+ colors[28]=0xffff0000;
+ colors[29]=0xffff00ff;
+ colors[30]=0xffffff00;
+ colors[31]=0xffffffff;
+ colors[32]=0xff000000;
+ colors[33]=0xff0000ff;
+ colors[34]=0xff00ff00;
+ colors[35]=0xff00ffff;
+ colors[36]=0xffff0000;
+ colors[37]=0xffff00ff;
+ colors[38]=0xffffff00;
+ colors[39]=0xffffffff;
+ colors[40]=0xff000000;
+ colors[41]=0xff0000ff;
+ colors[42]=0xff00ff00;
+ colors[43]=0xff00ffff;
+ colors[44]=0xffff0000;
+ colors[45]=0xffff00ff;
+ colors[46]=0xffffff00;
+ colors[47]=0xffffffff;
+ colors[48]=0xff000000;
+ colors[49]=0xff0000ff;
+ colors[50]=0xff00ff00;
+ colors[51]=0xff00ffff;
+ colors[52]=0xffff0000;
+ colors[53]=0xffff00ff;
+ colors[54]=0xffffff00;
+ colors[55]=0xffffffff;
+ colors[56]=0xff000000;
+ colors[57]=0xff0000ff;
+ colors[58]=0xff00ff00;
+ colors[59]=0xff00ffff;
+ colors[60]=0xffff0000;
+ colors[61]=0xffff00ff;
+ colors[62]=0xffffff00;
+ colors[63]=0xffffffff;
+ colors[64]=0xff000000;
+ colors[65]=0xff0000ff;
+ colors[66]=0xff00ff00;
+ colors[67]=0xff00ffff;
+ colors[68]=0xffff0000;
+ colors[69]=0xffff00ff;
+ colors[70]=0xffffff00;
+ colors[71]=0xffffffff;
+ colors[72]=0xff000000;
+ colors[73]=0xff0000ff;
+ colors[74]=0xff00ff00;
+ colors[75]=0xff00ffff;
+ colors[76]=0xffff0000;
+ colors[77]=0xffff00ff;
+ colors[78]=0xffffff00;
+ colors[79]=0xffffffff;
+ colors[80]=0xff000000;
+ colors[81]=0xff0000ff;
+ colors[82]=0xff00ff00;
+ colors[83]=0xff00ffff;
+ colors[84]=0xffff0000;
+ colors[85]=0xffff00ff;
+ colors[86]=0xffffff00;
+ colors[87]=0xffffffff;
+ colors[88]=0xff000000;
+ colors[89]=0xff0000ff;
+ colors[90]=0xff00ff00;
+ colors[91]=0xff00ffff;
+ colors[92]=0xffff0000;
+ colors[93]=0xffff00ff;
+ colors[94]=0xffffff00;
+ colors[95]=0xffffffff;
+ colors[96]=0xff000000;
+ colors[97]=0xff0000ff;
+ colors[98]=0xff00ff00;
+ colors[99]=0xff00ffff;
+ colors[100]=0xffff0000;
+ colors[101]=0xffff00ff;
+ colors[102]=0xffffff00;
+ colors[103]=0xffffffff;
+ colors[104]=0xff000000;
+ colors[105]=0xff0000ff;
+ colors[106]=0xff00ff00;
+ colors[107]=0xff00ffff;
+ colors[108]=0xffff0000;
+ colors[109]=0xffff00ff;
+ colors[110]=0xffffff00;
+ colors[111]=0xffffffff;
+ colors[112]=0xff000000;
+ colors[113]=0xff0000ff;
+ colors[114]=0xff00ff00;
+ colors[115]=0xff00ffff;
+ colors[116]=0xffff0000;
+ colors[117]=0xffff00ff;
+ colors[118]=0xffffff00;
+ colors[119]=0xffffffff;
+ colors[120]=0xff000000;
+ colors[121]=0xff0000ff;
+ colors[122]=0xff00ff00;
+ colors[123]=0xff00ffff;
+ colors[124]=0xffff0000;
+ colors[125]=0xffff00ff;
+ colors[126]=0xffffff00;
+ colors[127]=0xffffffff;
+ colors[128]=0xff000000;
+ colors[129]=0xff0000ff;
+ colors[130]=0xff00ff00;
+ colors[131]=0xff00ffff;
+ colors[132]=0xffff0000;
+ colors[133]=0xffff00ff;
+ colors[134]=0xffffff00;
+ colors[135]=0xffffffff;
+ colors[136]=0xff000000;
+ colors[137]=0xff0000ff;
+ colors[138]=0xff00ff00;
+ colors[139]=0xff00ffff;
+ colors[140]=0xffff0000;
+ colors[141]=0xffff00ff;
+ colors[142]=0xffffff00;
+ colors[143]=0xffffffff;
+ colors[144]=0xff000000;
+ colors[145]=0xff0000ff;
+ colors[146]=0xff00ff00;
+ colors[147]=0xff00ffff;
+ colors[148]=0xffff0000;
+ colors[149]=0xffff00ff;
+ colors[150]=0xffffff00;
+ colors[151]=0xffffffff;
+ colors[152]=0xff000000;
+ colors[153]=0xff0000ff;
+ colors[154]=0xff00ff00;
+ colors[155]=0xff00ffff;
+ colors[156]=0xffff0000;
+ colors[157]=0xffff00ff;
+ colors[158]=0xffffff00;
+ colors[159]=0xffffffff;
+ colors[160]=0xff000000;
+ colors[161]=0xff0000ff;
+ colors[162]=0xff00ff00;
+ colors[163]=0xff00ffff;
+ colors[164]=0xffff0000;
+ colors[165]=0xffff00ff;
+ colors[166]=0xffffff00;
+ colors[167]=0xffffffff;
+ colors[168]=0xff000000;
+ colors[169]=0xff0000ff;
+ colors[170]=0xff00ff00;
+ colors[171]=0xff00ffff;
+ colors[172]=0xffff0000;
+ colors[173]=0xffff00ff;
+ colors[174]=0xffffff00;
+ colors[175]=0xffffffff;
+ colors[176]=0xff000000;
+ colors[177]=0xff0000ff;
+ colors[178]=0xff00ff00;
+ colors[179]=0xff00ffff;
+ colors[180]=0xffff0000;
+ colors[181]=0xffff00ff;
+ colors[182]=0xffffff00;
+ colors[183]=0xffffffff;
+ colors[184]=0xff000000;
+ colors[185]=0xff0000ff;
+ colors[186]=0xff00ff00;
+ colors[187]=0xff00ffff;
+ colors[188]=0xffff0000;
+ colors[189]=0xffff00ff;
+ colors[190]=0xffffff00;
+ colors[191]=0xffffffff;
+ colors[192]=0xff000000;
+ colors[193]=0xff0000ff;
+ colors[194]=0xff00ff00;
+ colors[195]=0xff00ffff;
+ colors[196]=0xffff0000;
+ colors[197]=0xffff00ff;
+ colors[198]=0xffffff00;
+ colors[199]=0xffffffff;
+ colors[200]=0xff000000;
+ colors[201]=0xff0000ff;
+ colors[202]=0xff00ff00;
+ colors[203]=0xff00ffff;
+ colors[204]=0xffff0000;
+ colors[205]=0xffff00ff;
+ colors[206]=0xffffff00;
+ colors[207]=0xffffffff;
+ colors[208]=0xff000000;
+ colors[209]=0xff0000ff;
+ colors[210]=0xff00ff00;
+ colors[211]=0xff00ffff;
+ colors[212]=0xffff0000;
+ colors[213]=0xffff00ff;
+ colors[214]=0xffffff00;
+ colors[215]=0xffffffff;
+ colors[216]=0xff000000;
+ colors[217]=0xff0000ff;
+ colors[218]=0xff00ff00;
+ colors[219]=0xff00ffff;
+ colors[220]=0xffff0000;
+ colors[221]=0xffff00ff;
+ colors[222]=0xffffff00;
+ colors[223]=0xffffffff;
+ colors[224]=0xff000000;
+ colors[225]=0xff0000ff;
+ colors[226]=0xff00ff00;
+ colors[227]=0xff00ffff;
+ colors[228]=0xffff0000;
+ colors[229]=0xffff00ff;
+ colors[230]=0xffffff00;
+ colors[231]=0xffffffff;
+ colors[232]=0xff000000;
+ colors[233]=0xff0000ff;
+ colors[234]=0xff00ff00;
+ colors[235]=0xff00ffff;
+ colors[236]=0xffff0000;
+ colors[237]=0xffff00ff;
+ colors[238]=0xffffff00;
+ colors[239]=0xffffffff;
+ colors[240]=0xff000000;
+ colors[241]=0xff0000ff;
+ colors[242]=0xff00ff00;
+ colors[243]=0xff00ffff;
+ colors[244]=0xffff0000;
+ colors[245]=0xffff00ff;
+ colors[246]=0xffffff00;
+ colors[247]=0xffffffff;
+ colors[248]=0xff000000;
+ colors[249]=0xff0000ff;
+ colors[250]=0xff00ff00;
+ colors[251]=0xff00ffff;
+ colors[252]=0xffff0000;
+ colors[253]=0xffff00ff;
+ colors[254]=0xffffff00;
+ colors[255]=0xffffffff;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/CompactBitmapData.java b/app/src/main/java/android/androidVNC/CompactBitmapData.java
new file mode 100644
index 000000000..fb1d7af48
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/CompactBitmapData.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.io.IOException;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class CompactBitmapData extends AbstractBitmapData {
+
+ class CompactBitmapDrawable extends AbstractBitmapDrawable
+ {
+ CompactBitmapDrawable()
+ {
+ super(CompactBitmapData.this);
+ }
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas)
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ draw(canvas, 0, 0);
+ }
+ }
+
+ CompactBitmapData(RfbProto rfb, VncCanvas c)
+ {
+ super(rfb,c);
+ bitmapwidth=framebufferwidth;
+ bitmapheight=framebufferheight;
+ mbitmap = Bitmap.createBitmap(rfb.framebufferWidth, rfb.framebufferHeight, Bitmap.Config.RGB_565);
+ memGraphics = new Canvas(mbitmap);
+ bitmapPixels = new int[rfb.framebufferWidth * rfb.framebufferHeight];
+ }
+
+ @Override
+ void writeFullUpdateRequest(boolean incremental) throws IOException {
+ rfb.writeFramebufferUpdateRequest(0, 0, framebufferwidth, framebufferheight, incremental);
+ }
+
+ @Override
+ boolean validDraw(int x, int y, int w, int h) {
+ return true;
+ }
+
+ @Override
+ int offset(int x, int y) {
+ return y * bitmapwidth + x;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#createDrawable()
+ */
+ @Override
+ AbstractBitmapDrawable createDrawable() {
+ return new CompactBitmapDrawable();
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#updateBitmap(int, int, int, int)
+ */
+ @Override
+ void updateBitmap(int x, int y, int w, int h) {
+ mbitmap.setPixels(bitmapPixels, offset(x,y), bitmapwidth, x, y, w, h);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#copyRect(android.graphics.Rect, android.graphics.Rect, android.graphics.Paint)
+ */
+ @Override
+ void copyRect(Rect src, Rect dest, Paint paint) {
+ memGraphics.drawBitmap(mbitmap, src, dest, paint);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#drawRect(int, int, int, int, android.graphics.Paint)
+ */
+ @Override
+ void drawRect(int x, int y, int w, int h, Paint paint) {
+ memGraphics.drawRect(x, y, x + w, y + h, paint);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#scrollChanged(int, int)
+ */
+ @Override
+ void scrollChanged(int newx, int newy) {
+ // Don't need to do anything here
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#syncScroll()
+ */
+ @Override
+ void syncScroll() {
+ // Don't need anything here either
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ConnectionBean.java b/app/src/main/java/android/androidVNC/ConnectionBean.java
new file mode 100644
index 000000000..0a40c7daa
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ConnectionBean.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+import android.widget.ImageView.ScaleType;
+
+import com.antlersoft.android.dbimpl.NewInstance;
+
+import java.lang.Comparable;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class ConnectionBean extends AbstractConnectionBean implements Comparable {
+ static final NewInstance newInstance=new NewInstance() {
+ public ConnectionBean get() { return new ConnectionBean(); }
+ };
+ ConnectionBean()
+ {
+ set_Id(0);
+ setAddress("");
+ setPassword("");
+ setKeepPassword(true);
+ setNickname("");
+ setUserName("");
+ setPort(5900);
+ setColorModel(COLORMODEL.C64.nameString());
+ setScaleMode(ScaleType.MATRIX);
+ setInputMode(VncCanvasActivity.TOUCH_ZOOM_MODE);
+ setRepeaterId("");
+ setMetaListId(1);
+ }
+
+ boolean isNew()
+ {
+ return get_Id()== 0;
+ }
+
+ void save(SQLiteDatabase database) {
+ ContentValues values=Gen_getValues();
+ values.remove(GEN_FIELD__ID);
+ if ( ! getKeepPassword()) {
+ values.put(GEN_FIELD_PASSWORD, "");
+ }
+ if ( isNew()) {
+ set_Id(database.insert(GEN_TABLE_NAME, null, values));
+ } else {
+ database.update(GEN_TABLE_NAME, values, GEN_FIELD__ID + " = ?", new String[] { Long.toString(get_Id()) });
+ }
+ }
+
+ ScaleType getScaleMode()
+ {
+ return ScaleType.valueOf(getScaleModeAsString());
+ }
+
+ void setScaleMode(ScaleType value)
+ {
+ setScaleModeAsString(value.toString());
+ }
+
+ @Override
+ public String toString() {
+ if ( isNew())
+ {
+ return "New";
+ }
+ return getNickname()+":"+getAddress()+":"+getPort();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(ConnectionBean another) {
+ int result = getNickname().compareTo(another.getNickname());
+ if (result == 0) {
+ result = getAddress().compareTo(another.getAddress());
+ if ( result == 0) {
+ result = getPort() - another.getPort();
+ }
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/ConnectionListActivity.java b/app/src/main/java/android/androidVNC/ConnectionListActivity.java
new file mode 100644
index 000000000..7e8245812
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ConnectionListActivity.java
@@ -0,0 +1,120 @@
+/*
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ *
+ * Copyright 2009,2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.app.*;
+import android.content.*;
+import android.content.Intent.*;
+import android.database.*;
+import android.net.*;
+import android.os.*;
+import android.view.*;
+import android.widget.*;
+import net.kdt.pojavlaunch.*;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class ConnectionListActivity extends ListActivity {
+
+ VncDatabase database;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+
+ database = new VncDatabase(this);
+
+ // Query for all people contacts using the Contacts.People convenience class.
+ // Put a managed wrapper around the retrieved cursor so we don't have to worry about
+ // requerying or closing it as the activity changes state.
+ Cursor mCursor = database.getReadableDatabase().query(ConnectionBean.GEN_TABLE_NAME, new String[] {
+ ConnectionBean.GEN_FIELD__ID,
+ ConnectionBean.GEN_FIELD_NICKNAME,
+ ConnectionBean.GEN_FIELD_USERNAME,
+ ConnectionBean.GEN_FIELD_ADDRESS,
+ ConnectionBean.GEN_FIELD_PORT,
+ ConnectionBean.GEN_FIELD_REPEATERID },
+ ConnectionBean.GEN_FIELD_KEEPPASSWORD + " <> 0", null, null, null, ConnectionBean.GEN_FIELD_NICKNAME);
+ startManagingCursor(mCursor);
+
+ // Now create a new list adapter bound to the cursor.
+ // SimpleListAdapter is designed for binding to a Cursor.
+ SimpleCursorAdapter adapter = new SimpleCursorAdapter(
+ this, // Context.
+ R.layout.connection_list,
+ mCursor, // Pass in the cursor to bind to.
+ new String[] {
+ ConnectionBean.GEN_FIELD_NICKNAME,
+ ConnectionBean.GEN_FIELD_ADDRESS,
+ ConnectionBean.GEN_FIELD_PORT,
+ ConnectionBean.GEN_FIELD_REPEATERID }, // Array of cursor columns to bind to.
+ new int[] {
+ R.id.list_text_nickname,
+ R.id.list_text_address,
+ R.id.list_text_port,
+ R.id.list_text_repeater
+ }); // Parallel array of which template objects to bind to those columns.
+
+ // Bind to our new adapter.
+ setListAdapter(adapter);
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.ListActivity#onListItemClick(android.widget.ListView, android.view.View, int, long)
+ */
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ ConnectionBean connection = new ConnectionBean();
+ if (connection.Gen_read(database.getReadableDatabase(), id))
+ {
+ // create shortcut if requested
+ ShortcutIconResource icon = Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_xvnc);
+
+ Intent intent = new Intent();
+
+ Intent launchIntent = new Intent(this,VncCanvasActivity.class);
+ Uri.Builder builder = new Uri.Builder();
+ builder.authority(VncConstants.CONNECTION + ":" + connection.get_Id());
+ builder.scheme("vnc");
+ launchIntent.setData(builder.build());
+
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, connection.getNickname());
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
+
+ setResult(RESULT_OK, intent);
+ }
+ else
+ setResult(RESULT_CANCELED);
+
+ finish();
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Activity#onDestroy()
+ */
+ @Override
+ protected void onDestroy() {
+ database.close();
+ super.onDestroy();
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/ConnectionSettable.java b/app/src/main/java/android/androidVNC/ConnectionSettable.java
new file mode 100644
index 000000000..dfc64cc02
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ConnectionSettable.java
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package android.androidVNC;
+
+/**
+ * @author mike
+ *
+ */
+interface ConnectionSettable {
+ void setConnection(ConnectionBean connection);
+}
diff --git a/app/src/main/java/android/androidVNC/DH.java b/app/src/main/java/android/androidVNC/DH.java
new file mode 100644
index 000000000..aaf6b1099
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/DH.java
@@ -0,0 +1,183 @@
+package android.androidVNC;
+// CRYPTO LIBRARY FOR EXCHANGING KEYS
+// USING THE DIFFIE-HELLMAN KEY EXCHANGE PROTOCOL
+
+// The diffie-hellman can be used to securely exchange keys
+// between parties, where a third party eavesdropper given
+// the values being transmitted cannot determine the key.
+
+// Implemented by Lee Griffiths, Jan 2004.
+// This software is freeware, you may use it to your discretion,
+// however by doing so you take full responsibility for any damage
+// it may cause.
+
+// Hope you find it useful, even if you just use some of the functions
+// out of it like the prime number generator and the XtoYmodN function.
+
+// It would be great if you could send me emails to: lee.griffiths@first4internet.co.uk
+// with any suggestions, comments, or questions!
+
+// Enjoy.
+
+// Adopted to ms-logon for ultravnc and ported to Java by marscha, 2006.
+
+//import java.lang.Math;
+
+public class DH {
+
+ public DH() {
+ maxNum = (((long) 1) << DH_MAX_BITS) - 1;
+ }
+
+ public DH(long generator, long modulus) throws Exception {
+ maxNum = (((long) 1) << DH_MAX_BITS) - 1;
+ if (generator >= maxNum || modulus >= maxNum)
+ throw new Exception("Modulus or generator too large.");
+ gen = generator;
+ mod = modulus;
+ }
+
+ private long rng(long limit) {
+ return (long) (java.lang.Math.random() * limit);
+ }
+
+ //Performs the miller-rabin primality test on a guessed prime n.
+ //trials is the number of attempts to verify this, because the function
+ //is not 100% accurate it may be a composite. However setting the trial
+ //value to around 5 should guarantee success even with very large primes
+ private boolean millerRabin (long n, int trials) {
+ long a = 0;
+
+ for (int i = 0; i < trials; i++) {
+ a = rng(n - 3) + 2;// gets random value in [2..n-1]
+ if (XpowYmodN(a, n - 1, n) != 1) return false; //n composite, return false
+ }
+ return true; // n probably prime
+ }
+
+ //Generates a large prime number by
+ //choosing a randomly large integer, and ensuring the value is odd
+ //then uses the miller-rabin primality test on it to see if it is prime
+ //if not the value gets increased until it is prime
+ private long generatePrime() {
+ long prime = 0;
+
+ do {
+ long start = rng(maxNum);
+ prime = tryToGeneratePrime(start);
+ } while (prime == 0);
+ return prime;
+ }
+
+ private long tryToGeneratePrime(long prime) {
+ //ensure it is an odd number
+ if ((prime & 1) == 0)
+ prime += 1;
+
+ long cnt = 0;
+ while (!millerRabin(prime, 25) && (cnt++ < DH_RANGE) && prime < maxNum) {
+ prime += 2;
+ if ((prime % 3) == 0) prime += 2;
+ }
+ return (cnt >= DH_RANGE || prime >= maxNum) ? 0 : prime;
+ }
+
+ //Raises X to the power Y in modulus N
+ //the values of X, Y, and N can be massive, and this can be
+ //achieved by first calculating X to the power of 2 then
+ //using power chaining over modulus N
+ private long XpowYmodN(long x, long y, long N) {
+ long result = 1;
+ final long oneShift63 = ((long) 1) << 63;
+
+ for (int i = 0; i < 64; y <<= 1, i++){
+ result = result * result % N;
+ if ((y & oneShift63) != 0)
+ result = result * x % N;
+ }
+ return result;
+ }
+
+ public void createKeys() {
+ gen = generatePrime();
+ mod = generatePrime();
+
+ if (gen > mod) {
+ long swap = gen;
+ gen = mod;
+ mod = swap;
+ }
+ }
+
+ public long createInterKey() {
+ priv = rng(maxNum);
+ return pub = XpowYmodN(gen,priv,mod);
+ }
+
+ public long createEncryptionKey(long interKey) throws Exception {
+ if (interKey >= maxNum){
+ throw new Exception("interKey too large");
+ }
+ return key = XpowYmodN(interKey,priv,mod);
+ }
+
+
+ public long getValue(int flags) {
+ switch (flags) {
+ case DH_MOD:
+ return mod;
+ case DH_GEN:
+ return gen;
+ case DH_PRIV:
+ return priv;
+ case DH_PUB:
+ return pub;
+ case DH_KEY:
+ return key;
+ default:
+ return (long) 0;
+ }
+ }
+
+ public int bits(long number){
+ for (int i = 0; i < 64; i++){
+ number /= 2;
+ if (number < 2) return i;
+ }
+ return 0;
+ }
+
+ public static byte[] longToBytes(long number) {
+ byte[] bytes = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ bytes[i] = (byte) (0xff & (number >> (8 * (7 - i))));
+ }
+ return bytes;
+ }
+
+ public static long bytesToLong(byte[] bytes) {
+ long result = 0;
+ for (int i = 0; i < 8; i++) {
+ result <<= 8;
+ result += (byte) bytes[i];
+ }
+ return result;
+ }
+
+ private long gen;
+ private long mod;
+ private long priv;
+ private long pub;
+ private long key;
+ private long maxNum;
+
+ private static final int DH_MAX_BITS = 31;
+ private static final int DH_RANGE = 100;
+
+ private static final int DH_MOD = 1;
+ private static final int DH_GEN = 2;
+ private static final int DH_PRIV = 3;
+ private static final int DH_PUB = 4;
+ private static final int DH_KEY = 5;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/android/androidVNC/DPadMouseKeyHandler.java b/app/src/main/java/android/androidVNC/DPadMouseKeyHandler.java
new file mode 100644
index 000000000..db3cfd2aa
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/DPadMouseKeyHandler.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.PointF;
+import android.os.Handler;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Input handlers delegate to this class to handle keystrokes; this detects keystrokes
+ * from the DPad and uses them to perform mouse actions; other keystrokes are passed to
+ * VncCanvasActivity.defaultKeyXXXHandler
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+class DPadMouseKeyHandler {
+ private MouseMover mouseMover;
+ private boolean mouseDown;
+ private VncCanvasActivity activity;
+ private VncCanvas canvas;
+ private boolean isMoving;
+
+ DPadMouseKeyHandler(VncCanvasActivity activity, Handler handler)
+ {
+ this.activity = activity;
+ canvas = activity.vncCanvas;
+ mouseMover = new MouseMover(activity, handler);
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ int xv = 0;
+ int yv = 0;
+ boolean result = true;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ xv = -1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ xv = 1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ yv = -1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ yv = 1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (!mouseDown) {
+ mouseDown = true;
+ result = canvas.processPointerEvent(canvas.mouseX, canvas.mouseY, MotionEvent.ACTION_DOWN, evt.getMetaState(), mouseDown, canvas.cameraButtonDown);
+ }
+ break;
+ default:
+ result = activity.defaultKeyDownHandler(keyCode, evt);
+ break;
+ }
+ if ((xv != 0 || yv != 0) && !isMoving) {
+ final int x = xv;
+ final int y = yv;
+ isMoving = true;
+ mouseMover.start(x, y, new Panner.VelocityUpdater() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.Panner.VelocityUpdater#updateVelocity(android.graphics.Point,
+ * long)
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ double scale = (1.2 * (double) interval / 50.0);
+ if (Math.abs(p.x) < 500)
+ p.x += (int) (scale * x);
+ if (Math.abs(p.y) < 500)
+ p.y += (int) (scale * y);
+ return true;
+ }
+
+ });
+ canvas.processPointerEvent(canvas.mouseX + x, canvas.mouseY + y, MotionEvent.ACTION_MOVE, evt.getMetaState(), mouseDown, canvas.cameraButtonDown);
+
+ }
+ return result;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ boolean result = false;
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ mouseMover.stop();
+ isMoving = false;
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (mouseDown) {
+ mouseDown = false;
+ result = canvas.processPointerEvent(canvas.mouseX, canvas.mouseY, MotionEvent.ACTION_UP, evt.getMetaState(), mouseDown, canvas.cameraButtonDown);
+ } else {
+ result = true;
+ }
+ break;
+ default:
+ result = activity.defaultKeyUpHandler(keyCode, evt);
+ break;
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/DesCipher.java b/app/src/main/java/android/androidVNC/DesCipher.java
new file mode 100644
index 000000000..2d7bed357
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/DesCipher.java
@@ -0,0 +1,539 @@
+//
+// This DES class has been extracted from package Acme.Crypto for use in VNC.
+// The bytebit[] array has been reversed so that the most significant bit
+// in each byte of the key is ignored, not the least significant. Also the
+// unnecessary odd parity code has been removed.
+//
+// These changes are:
+// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+//
+
+// DesCipher - the DES encryption method
+//
+// The meat of this code is by Dave Zimmerman , and is:
+//
+// Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+//
+// Permission to use, copy, modify, and distribute this software
+// and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+// without fee is hereby granted, provided that this copyright notice is kept
+// intact.
+//
+// WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+// OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+// FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+// DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+//
+// THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+// CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+// PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+// NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+// SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+// SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+// PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
+// SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+// HIGH RISK ACTIVITIES.
+//
+//
+// The rest is:
+//
+// Copyright (C) 1996 by Jef Poskanzer . All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+/// The DES encryption method.
+//
+// This is surprisingly fast, for pure Java. On a SPARC 20, wrapped
+// in Acme.Crypto.EncryptedOutputStream or Acme.Crypto.EncryptedInputStream,
+// it does around 7000 bytes/second.
+//
+// Most of this code is by Dave Zimmerman , and is
+// Copyright (c) 1996 Widget Workshop, Inc. See the source file for details.
+//
+ * For historical reasons, this is named as if it were just a boolean selection for auto and force full.
+ * @return 0 for auto, 1 for force full bitmap, 2 for forced tiled
+ */
+ @FieldAccessor
+ long getForceFull();
+ @FieldAccessor
+ String getRepeaterId();
+ @FieldAccessor
+ String getInputMode();
+ @FieldAccessor(Name="SCALEMODE")
+ String getScaleModeAsString();
+ @FieldAccessor
+ boolean getUseLocalCursor();
+ @FieldAccessor
+ boolean getKeepPassword();
+ @FieldAccessor
+ boolean getFollowMouse();
+ @FieldAccessor
+ boolean getUseRepeater();
+ @FieldAccessor
+ long getMetaListId();
+ @FieldAccessor(Name="LAST_META_KEY_ID")
+ long getLastMetaKeyId();
+ @FieldAccessor(DefaultValue="false")
+ boolean getFollowPan();
+ @FieldAccessor
+ String getUserName();
+ @FieldAccessor
+ String getSecureConnectionType();
+ @FieldAccessor(DefaultValue="true")
+ boolean getShowZoomButtons();
+ @FieldAccessor(Name="DOUBLE_TAP_ACTION")
+ String getDoubleTapActionAsString();
+}
diff --git a/app/src/main/java/android/androidVNC/IMetaKey.java b/app/src/main/java/android/androidVNC/IMetaKey.java
new file mode 100644
index 000000000..5d0497132
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/IMetaKey.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import com.antlersoft.android.db.FieldAccessor;
+import com.antlersoft.android.db.TableInterface;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+@TableInterface(TableName="META_KEY",ImplementingClassName="AbstractMetaKeyBean")
+public interface IMetaKey {
+ @FieldAccessor
+ long get_Id();
+ @FieldAccessor
+ long getMetaListId();
+ @FieldAccessor
+ String getKeyDesc();
+ @FieldAccessor
+ int getMetaFlags();
+ @FieldAccessor
+ boolean isMouseClick();
+ @FieldAccessor
+ int getMouseButtons();
+ @FieldAccessor
+ int getKeySym();
+ @FieldAccessor
+ String getShortcut();
+}
diff --git a/app/src/main/java/android/androidVNC/IMetaList.java b/app/src/main/java/android/androidVNC/IMetaList.java
new file mode 100644
index 000000000..ded2d0e9f
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/IMetaList.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import com.antlersoft.android.db.FieldAccessor;
+import com.antlersoft.android.db.TableInterface;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+@TableInterface(TableName="META_LIST",ImplementingIsAbstract=false,ImplementingClassName="MetaList")
+public interface IMetaList {
+ @FieldAccessor
+ long get_Id();
+ @FieldAccessor
+ String getName();
+}
diff --git a/app/src/main/java/android/androidVNC/IMostRecentBean.java b/app/src/main/java/android/androidVNC/IMostRecentBean.java
new file mode 100644
index 000000000..fd1970e01
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/IMostRecentBean.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import com.antlersoft.android.db.*;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+@TableInterface(TableName="MOST_RECENT",ImplementingIsAbstract=false,ImplementingClassName="MostRecentBean")
+public interface IMostRecentBean {
+ @FieldAccessor
+ long get_Id();
+ @FieldAccessor(Name="CONNECTION_ID")
+ long getConnectionId();
+ @FieldAccessor(Name="SHOW_SPLASH_VERSION")
+ long getShowSplashVersion();
+ @FieldAccessor(Name="TEXT_INDEX")
+ long getTextIndex();
+}
diff --git a/app/src/main/java/android/androidVNC/ISentText.java b/app/src/main/java/android/androidVNC/ISentText.java
new file mode 100644
index 000000000..a7ad9f22d
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ISentText.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import com.antlersoft.android.db.FieldAccessor;
+import com.antlersoft.android.db.TableInterface;
+/**
+ * Interface specification for table storing sent text; the last N text items sent are stored in a table
+ * and will be recalled on demand
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+@TableInterface(TableName="SENT_TEXT",ImplementingIsAbstract=false,ImplementingClassName="SentTextBean")
+public interface ISentText {
+ @FieldAccessor
+ long get_Id();
+ @FieldAccessor
+ String getSentText();
+}
diff --git a/app/src/main/java/android/androidVNC/ImportExportDialog.java b/app/src/main/java/android/androidVNC/ImportExportDialog.java
new file mode 100644
index 000000000..0efbb63f2
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ImportExportDialog.java
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.app.*;
+import android.os.*;
+import android.util.*;
+import android.view.*;
+import android.widget.*;
+import com.antlersoft.android.bc.*;
+import com.antlersoft.android.contentxml.*;
+import com.antlersoft.android.contentxml.SqliteElement.*;
+import java.io.*;
+import java.net.*;
+import net.kdt.pojavlaunch.*;
+import org.xml.sax.*;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class ImportExportDialog extends Dialog {
+
+ private androidVNC _configurationDialog;
+ private EditText _textLoadUrl;
+ private EditText _textSaveUrl;
+
+
+ /**
+ * @param context
+ */
+ public ImportExportDialog(androidVNC context) {
+ super(context);
+ setOwnerActivity((Activity)context);
+ _configurationDialog = context;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.importexport);
+ setTitle(R.string.import_export_settings);
+ _textLoadUrl = (EditText)findViewById(R.id.textImportUrl);
+ _textSaveUrl = (EditText)findViewById(R.id.textExportPath);
+
+ File f = BCFactory.getInstance().getStorageContext().getExternalStorageDir(_configurationDialog, null);
+ // Sdcard not mounted; nothing else to do
+ if (f == null)
+ return;
+
+ f = new File(f, "vnc_settings.xml");
+
+ _textSaveUrl.setText(f.getAbsolutePath());
+ try {
+ _textLoadUrl.setText(f.toURL().toString());
+ } catch (MalformedURLException e) {
+ // Do nothing; default value not set
+ }
+
+ Button export = (Button)findViewById(R.id.buttonExport);
+ export.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ try {
+ File f = new File(_textSaveUrl.getText().toString());
+ Writer writer = new OutputStreamWriter(new FileOutputStream(f, false));
+ SqliteElement.exportDbAsXmlToStream(_configurationDialog.getDatabaseHelper().getReadableDatabase(), writer);
+ writer.close();
+ dismiss();
+ }
+ catch (IOException ioe)
+ {
+ errorNotify("I/O Exception exporting config", ioe);
+ } catch (SAXException e) {
+ errorNotify("XML Exception exporting config", e);
+ }
+ }
+
+ });
+
+ ((Button)findViewById(R.id.buttonImport)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ try
+ {
+ URL url = new URL(_textLoadUrl.getText().toString());
+ URLConnection connection = url.openConnection();
+ connection.connect();
+ Reader reader = new InputStreamReader(connection.getInputStream());
+ SqliteElement.importXmlStreamToDb(
+ _configurationDialog.getDatabaseHelper().getWritableDatabase(),
+ reader,
+ ReplaceStrategy.REPLACE_EXISTING);
+ dismiss();
+ _configurationDialog.arriveOnPage();
+ }
+ catch (MalformedURLException mfe)
+ {
+ errorNotify("Improper URL given: " + _textLoadUrl.getText(), mfe);
+ }
+ catch (IOException ioe)
+ {
+ errorNotify("I/O error reading configuration", ioe);
+ }
+ catch (SAXException e)
+ {
+ errorNotify("XML or format error reading configuration", e);
+ }
+ }
+
+ });
+ }
+
+ private void errorNotify(String msg, Throwable t)
+ {
+ Log.i("android.androidVNC.ImportExportDialog", msg, t);
+ Utils.showErrorMessage(this.getContext(), msg + ":" + t.getMessage());
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/InStream.java b/app/src/main/java/android/androidVNC/InStream.java
new file mode 100644
index 000000000..cbbda0667
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/InStream.java
@@ -0,0 +1,155 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// rdr::InStream marshalls data from a buffer stored in RDR (RFB Data
+// Representation).
+//
+package android.androidVNC;
+
+
+abstract public class InStream {
+
+ // check() ensures there is buffer data for at least one item of size
+ // itemSize bytes. Returns the number of items in the buffer (up to a
+ // maximum of nItems).
+
+ public final int check(int itemSize, int nItems) throws Exception {
+ if (ptr + itemSize * nItems > end) {
+ if (ptr + itemSize > end)
+ return overrun(itemSize, nItems);
+
+ nItems = (end - ptr) / itemSize;
+ }
+ return nItems;
+ }
+
+ public final void check(int itemSize) throws Exception {
+ if (ptr + itemSize > end)
+ overrun(itemSize, 1);
+ }
+
+ // readU/SN() methods read unsigned and signed N-bit integers.
+
+ public final int readS8() throws Exception {
+ check(1); return b[ptr++];
+ }
+
+ public final int readS16() throws Exception {
+ check(2); int b0 = b[ptr++];
+ int b1 = b[ptr++] & 0xff; return b0 << 8 | b1;
+ }
+
+ public final int readS32() throws Exception {
+ check(4); int b0 = b[ptr++];
+ int b1 = b[ptr++] & 0xff;
+ int b2 = b[ptr++] & 0xff;
+ int b3 = b[ptr++] & 0xff;
+ return b0 << 24 | b1 << 16 | b2 << 8 | b3;
+ }
+
+ public final int readU8() throws Exception {
+ return readS8() & 0xff;
+ }
+
+ public final int readU16() throws Exception {
+ return readS16() & 0xffff;
+ }
+
+ public final int readU32() throws Exception {
+ return readS32() & 0xffffffff;
+ }
+
+ public final void skip(int bytes) throws Exception {
+ while (bytes > 0) {
+ int n = check(1, bytes);
+ ptr += n;
+ bytes -= n;
+ }
+ }
+
+ // readBytes() reads an exact number of bytes into an array at an offset.
+
+ public void readBytes(byte[] data, int offset, int length) throws Exception {
+ int offsetEnd = offset + length;
+ while (offset < offsetEnd) {
+ int n = check(1, offsetEnd - offset);
+ System.arraycopy(b, ptr, data, offset, n);
+ ptr += n;
+ offset += n;
+ }
+ }
+
+ // readOpaqueN() reads a quantity "without byte-swapping". Because java has
+ // no byte-ordering, we just use big-endian.
+
+ public final int readOpaque8() throws Exception {
+ return readU8();
+ }
+
+ public final int readOpaque16() throws Exception {
+ return readU16();
+ }
+
+ public final int readOpaque32() throws Exception {
+ return readU32();
+ }
+
+ public final int readOpaque24A() throws Exception {
+ check(3); int b0 = b[ptr++];
+ int b1 = b[ptr++]; int b2 = b[ptr++];
+ return b0 << 24 | b1 << 16 | b2 << 8;
+ }
+
+ public final int readOpaque24B() throws Exception {
+ check(3); int b0 = b[ptr++];
+ int b1 = b[ptr++]; int b2 = b[ptr++];
+ return b0 << 16 | b1 << 8 | b2;
+ }
+
+ // pos() returns the position in the stream.
+
+ abstract public int pos();
+
+ // bytesAvailable() returns true if at least one byte can be read from the
+ // stream without blocking. i.e. if false is returned then readU8() would
+ // block.
+
+ public boolean bytesAvailable() { return end != ptr; }
+
+ // getbuf(), getptr(), getend() and setptr() are "dirty" methods which allow
+ // you to manipulate the buffer directly. This is useful for a stream which
+ // is a wrapper around an underlying stream.
+
+ public final byte[] getbuf() { return b; }
+ public final int getptr() { return ptr; }
+ public final int getend() { return end; }
+ public final void setptr(int p) { ptr = p; }
+
+ // overrun() is implemented by a derived class to cope with buffer overrun.
+ // It ensures there are at least itemSize bytes of buffer data. Returns
+ // the number of items in the buffer (up to a maximum of nItems). itemSize
+ // is supposed to be "small" (a few bytes).
+
+ abstract protected int overrun(int itemSize, int nItems) throws Exception;
+
+ protected InStream() {}
+ protected byte[] b;
+ protected int ptr;
+ protected int end;
+}
diff --git a/app/src/main/java/android/androidVNC/IntroTextDialog.java b/app/src/main/java/android/androidVNC/IntroTextDialog.java
new file mode 100644
index 000000000..2d36769cc
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/IntroTextDialog.java
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.app.*;
+import android.content.pm.*;
+import android.database.sqlite.*;
+import android.os.*;
+import android.text.*;
+import android.text.method.*;
+import android.view.*;
+import android.view.MenuItem.*;
+import android.widget.*;
+import net.kdt.pojavlaunch.*;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class IntroTextDialog extends Dialog {
+
+ private PackageInfo packageInfo;
+ private VncDatabase database;
+
+ static IntroTextDialog dialog;
+
+ static void showIntroTextIfNecessary(Activity context, VncDatabase database)
+ {
+ PackageInfo pi;
+ try
+ {
+ pi = context.getPackageManager().getPackageInfo("android.androidVNC", 0);
+ }
+ catch (PackageManager.NameNotFoundException nnfe)
+ {
+ return;
+ }
+ MostRecentBean mr = androidVNC.getMostRecent(database.getReadableDatabase());
+ if (mr == null || mr.getShowSplashVersion() != pi.versionCode)
+ {
+ if (dialog == null)
+ {
+ dialog = new IntroTextDialog(context, pi, database);
+ dialog.show();
+ }
+ }
+ }
+
+ /**
+ * @param context -- Containing dialog
+ */
+ private IntroTextDialog(Activity context, PackageInfo pi, VncDatabase database) {
+ super(context);
+ setOwnerActivity(context);
+ packageInfo = pi;
+ this.database = database;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.intro_dialog);
+ StringBuilder sb = new StringBuilder(getContext().getResources().getString(R.string.intro_title));
+ sb.append(" ");
+ sb.append(packageInfo.versionName);
+ setTitle(sb);
+ sb.delete(0, sb.length());
+ sb.append(getContext().getResources().getString(R.string.intro_text));
+ sb.append(packageInfo.versionName);
+ sb.append(getContext().getResources().getString(R.string.intro_version_text));
+ TextView introTextView = (TextView)findViewById(R.id.textIntroText);
+ introTextView.setText(Html.fromHtml(sb.toString()));
+ introTextView.setMovementMethod(LinkMovementMethod.getInstance());
+ ((Button)findViewById(R.id.buttonCloseIntro)).setOnClickListener(new View.OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+
+ });
+ ((Button)findViewById(R.id.buttonCloseIntroDontShow)).setOnClickListener(new View.OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ dontShowAgain();
+ }
+
+ });
+
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getOwnerActivity().getMenuInflater().inflate(R.menu.intro_dialog_menu,menu);
+ menu.findItem(R.id.itemOpenDoc).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ Utils.showDocumentation(getOwnerActivity());
+ dismiss();
+ return true;
+ }
+ });
+ menu.findItem(R.id.itemClose).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ dismiss();
+ return true;
+ }
+ });
+ menu.findItem(R.id.itemDontShowAgain).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ dontShowAgain();
+ return true;
+ }
+ });
+ return true;
+ }
+
+ private void dontShowAgain()
+ {
+ SQLiteDatabase db = database.getWritableDatabase();
+ MostRecentBean mostRecent = androidVNC.getMostRecent(db);
+ if (mostRecent != null)
+ {
+ mostRecent.setShowSplashVersion(packageInfo.versionCode);
+ mostRecent.Gen_update(db);
+ }
+ dismiss();
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/LargeBitmapData.java b/app/src/main/java/android/androidVNC/LargeBitmapData.java
new file mode 100644
index 000000000..4b8e688e8
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/LargeBitmapData.java
@@ -0,0 +1,318 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.io.IOException;
+
+import com.antlersoft.android.drawing.OverlappingCopy;
+import com.antlersoft.android.drawing.RectList;
+import com.antlersoft.util.ObjectPool;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class LargeBitmapData extends AbstractBitmapData {
+
+ /**
+ * Multiply this times total number of pixels to get estimate of process size with all buffers plus
+ * safety factor
+ */
+ static final int CAPACITY_MULTIPLIER = 21;
+
+ int xoffset;
+ int yoffset;
+ int scrolledToX;
+ int scrolledToY;
+ private Rect bitmapRect;
+ private Paint defaultPaint;
+ private RectList invalidList;
+ private RectList pendingList;
+
+ /**
+ * Pool of temporary rectangle objects. Need to synchronize externally access from
+ * multiple threads.
+ */
+ private static ObjectPool rectPool = new ObjectPool() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#itemForPool()
+ */
+ @Override
+ protected Rect itemForPool() {
+ return new Rect();
+ }
+ };
+
+ class LargeBitmapDrawable extends AbstractBitmapDrawable
+ {
+ LargeBitmapDrawable()
+ {
+ super(LargeBitmapData.this);
+ }
+ /* (non-Javadoc)
+ * @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas)
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ //android.util.Log.i("LBM", "Drawing "+xoffset+" "+yoffset);
+ int xoff, yoff;
+ synchronized ( LargeBitmapData.this )
+ {
+ xoff=xoffset;
+ yoff=yoffset;
+ }
+ draw(canvas, xoff, yoff);
+ }
+ }
+
+ /**
+ *
+ * @param p Protocol implementation
+ * @param c View that will display screen
+ * @param displayWidth
+ * @param displayHeight
+ * @param capacity Max process heap size in bytes
+ */
+ LargeBitmapData(RfbProto p, VncCanvas c, int displayWidth, int displayHeight, int capacity)
+ {
+ super(p,c);
+ double scaleMultiplier = Math.sqrt((double)(capacity * 1024 * 1024) / (double)(CAPACITY_MULTIPLIER * framebufferwidth * framebufferheight));
+ if (scaleMultiplier > 1)
+ scaleMultiplier = 1;
+ bitmapwidth=(int)((double)framebufferwidth * scaleMultiplier);
+ if (bitmapwidth < displayWidth)
+ bitmapwidth = displayWidth;
+ bitmapheight=(int)((double)framebufferheight * scaleMultiplier);
+ if (bitmapheight < displayHeight)
+ bitmapheight = displayHeight;
+ android.util.Log.i("LBM", "bitmapsize = ("+bitmapwidth+","+bitmapheight+")");
+ mbitmap = Bitmap.createBitmap(bitmapwidth, bitmapheight, Bitmap.Config.RGB_565);
+ memGraphics = new Canvas(mbitmap);
+ bitmapPixels = new int[bitmapwidth * bitmapheight];
+ invalidList = new RectList(rectPool);
+ pendingList = new RectList(rectPool);
+ bitmapRect=new Rect(0,0,bitmapwidth,bitmapheight);
+ defaultPaint = new Paint();
+ }
+
+ @Override
+ AbstractBitmapDrawable createDrawable()
+ {
+ return new LargeBitmapDrawable();
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#copyRect(android.graphics.Rect, android.graphics.Rect, android.graphics.Paint)
+ */
+ @Override
+ void copyRect(Rect src, Rect dest, Paint paint) {
+ // TODO copy rect working?
+ throw new RuntimeException( "copyrect Not implemented");
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#drawRect(int, int, int, int, android.graphics.Paint)
+ */
+ @Override
+ void drawRect(int x, int y, int w, int h, Paint paint) {
+ x-=xoffset;
+ y-=yoffset;
+ memGraphics.drawRect(x, y, x+w, y+h, paint);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#offset(int, int)
+ */
+ @Override
+ int offset(int x, int y) {
+ return (y - yoffset) * bitmapwidth + x - xoffset;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#scrollChanged(int, int)
+ */
+ @Override
+ synchronized void scrollChanged(int newx, int newy) {
+ //android.util.Log.i("LBM","scroll "+newx+" "+newy);
+ int newScrolledToX = scrolledToX;
+ int newScrolledToY = scrolledToY;
+ int visibleWidth = vncCanvas.getVisibleWidth();
+ int visibleHeight = vncCanvas.getVisibleHeight();
+ if (newx - xoffset < 0 )
+ {
+ newScrolledToX = newx + visibleWidth / 2 - bitmapwidth / 2;
+ if (newScrolledToX < 0)
+ newScrolledToX = 0;
+ }
+ else if (newx - xoffset + visibleWidth > bitmapwidth)
+ {
+ newScrolledToX = newx + visibleWidth / 2 - bitmapwidth / 2;
+ if (newScrolledToX + bitmapwidth > framebufferwidth)
+ newScrolledToX = framebufferwidth - bitmapwidth;
+ }
+ if (newy - yoffset < 0 )
+ {
+ newScrolledToY = newy + visibleHeight / 2 - bitmapheight / 2;
+ if (newScrolledToY < 0)
+ newScrolledToY = 0;
+ }
+ else if (newy - yoffset + visibleHeight > bitmapheight)
+ {
+ newScrolledToY = newy + visibleHeight / 2 - bitmapheight / 2;
+ if (newScrolledToY + bitmapheight > framebufferheight)
+ newScrolledToY = framebufferheight - bitmapheight;
+ }
+ if (newScrolledToX != scrolledToX || newScrolledToY != scrolledToY)
+ {
+ scrolledToX = newScrolledToX;
+ scrolledToY = newScrolledToY;
+ if ( waitingForInput)
+ syncScroll();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#updateBitmap(int, int, int, int)
+ */
+ @Override
+ void updateBitmap(int x, int y, int w, int h) {
+ mbitmap.setPixels(bitmapPixels, offset(x,y), bitmapwidth, x-xoffset, y-yoffset, w, h);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#validDraw(int, int, int, int)
+ */
+ @Override
+ synchronized boolean validDraw(int x, int y, int w, int h) {
+ //android.util.Log.i("LBM", "Validate Drawing "+x+" "+y+" "+w+" "+h+" "+xoffset+" "+yoffset+" "+(x-xoffset>=0 && x-xoffset+w<=bitmapwidth && y-yoffset>=0 && y-yoffset+h<=bitmapheight));
+ boolean result = x-xoffset>=0 && x-xoffset+w<=bitmapwidth && y-yoffset>=0 && y-yoffset+h<=bitmapheight;
+ ObjectPool.Entry entry = rectPool.reserve();
+ Rect r = entry.get();
+ r.set(x, y, x+w, y+h);
+ pendingList.subtract(r);
+ if ( ! result)
+ {
+ invalidList.add(r);
+ }
+ else
+ invalidList.subtract(r);
+ rectPool.release(entry);
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#writeFullUpdateRequest(boolean)
+ */
+ @Override
+ synchronized void writeFullUpdateRequest(boolean incremental) throws IOException {
+ if (! incremental) {
+ ObjectPool.Entry entry = rectPool.reserve();
+ Rect r = entry.get();
+ r.left=xoffset;
+ r.top=yoffset;
+ r.right=xoffset + bitmapwidth;
+ r.bottom=yoffset + bitmapheight;
+ pendingList.add(r);
+ invalidList.add(r);
+ rectPool.release(entry);
+ }
+ rfb.writeFramebufferUpdateRequest(xoffset, yoffset, bitmapwidth, bitmapheight, incremental);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractBitmapData#syncScroll()
+ */
+ @Override
+ synchronized void syncScroll() {
+
+ int deltaX = xoffset - scrolledToX;
+ int deltaY = yoffset - scrolledToY;
+ xoffset=scrolledToX;
+ yoffset=scrolledToY;
+ bitmapRect.top=scrolledToY;
+ bitmapRect.bottom=scrolledToY+bitmapheight;
+ bitmapRect.left=scrolledToX;
+ bitmapRect.right=scrolledToX+bitmapwidth;
+ invalidList.intersect(bitmapRect);
+ if ( deltaX != 0 || deltaY != 0)
+ {
+ boolean didOverlapping = false;
+ if (Math.abs(deltaX) < bitmapwidth && Math.abs(deltaY) < bitmapheight) {
+ ObjectPool.Entry sourceEntry = rectPool.reserve();
+ ObjectPool.Entry addedEntry = rectPool.reserve();
+ try
+ {
+ Rect added = addedEntry.get();
+ Rect sourceRect = sourceEntry.get();
+ sourceRect.set(deltaX<0 ? -deltaX : 0,
+ deltaY<0 ? -deltaY : 0,
+ deltaX<0 ? bitmapwidth : bitmapwidth - deltaX,
+ deltaY < 0 ? bitmapheight : bitmapheight - deltaY);
+ if (! invalidList.testIntersect(sourceRect)) {
+ didOverlapping = true;
+ OverlappingCopy.Copy(mbitmap, memGraphics, defaultPaint, sourceRect, deltaX + sourceRect.left, deltaY + sourceRect.top, rectPool);
+ // Write request for side pixels
+ if (deltaX != 0) {
+ added.left = deltaX < 0 ? bitmapRect.right + deltaX : bitmapRect.left;
+ added.right = added.left + Math.abs(deltaX);
+ added.top = bitmapRect.top;
+ added.bottom = bitmapRect.bottom;
+ invalidList.add(added);
+ }
+ if (deltaY != 0) {
+ added.left = deltaX < 0 ? bitmapRect.left : bitmapRect.left + deltaX;
+ added.top = deltaY < 0 ? bitmapRect.bottom + deltaY : bitmapRect.top;
+ added.right = added.left + bitmapwidth - Math.abs(deltaX);
+ added.bottom = added.top + Math.abs(deltaY);
+ invalidList.add(added);
+ }
+ }
+ }
+ finally {
+ rectPool.release(addedEntry);
+ rectPool.release(sourceEntry);
+ }
+ }
+ if (! didOverlapping)
+ {
+ try
+ {
+ //android.util.Log.i("LBM","update req "+xoffset+" "+yoffset);
+ mbitmap.eraseColor(Color.GREEN);
+ writeFullUpdateRequest(false);
+ }
+ catch ( IOException ioe)
+ {
+ // TODO log this
+ }
+ }
+ }
+ int size = pendingList.getSize();
+ for (int i=0; i {
+ int keySym;
+ int mouseButtons;
+ int keyEvent;
+ String name;
+ boolean isMouse;
+ boolean isKeyEvent;
+
+ MetaKeyBase(int mouseButtons, String name)
+ {
+ this.mouseButtons = mouseButtons;
+ this.name = name;
+ this.isMouse = true;
+ this.isKeyEvent = false;
+ }
+
+ MetaKeyBase(String name, int keySym, int keyEvent)
+ {
+ this.name = name;
+ this.keySym = keySym;
+ this.keyEvent = keyEvent;
+ this.isMouse = false;
+ this.isKeyEvent = true;
+ }
+
+ MetaKeyBase(String name, int keySym)
+ {
+ this.name = name;
+ this.keySym = keySym;
+ this.isMouse = false;
+ this.isKeyEvent = false;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(MetaKeyBase another) {
+ return name.compareTo(another.name);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/MetaKeyBean.java b/app/src/main/java/android/androidVNC/MetaKeyBean.java
new file mode 100644
index 000000000..703c46569
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MetaKeyBean.java
@@ -0,0 +1,269 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.antlersoft.android.dbimpl.NewInstance;
+
+import android.view.KeyEvent;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class MetaKeyBean extends AbstractMetaKeyBean implements Comparable {
+ static final ArrayList allKeys;
+ static final String[] allKeysNames;
+ static final HashMap keysByKeyCode;
+ static final HashMap keysByMouseButton;
+ static final HashMap keysByKeySym;
+ static final MetaKeyBean keyCtrlAltDel;
+ static final MetaKeyBean keyArrowLeft;
+ static final MetaKeyBean keyArrowRight;
+ static final MetaKeyBean keyArrowUp;
+ static final MetaKeyBean keyArrowDown;
+
+ static final NewInstance NEW;
+
+ static {
+ allKeys = new ArrayList();
+
+ allKeys.add(new MetaKeyBase("Hangul", 0xff31));
+ allKeys.add(new MetaKeyBase("Hangul_Start", 0xff32));
+ allKeys.add(new MetaKeyBase("Hangul_End", 0xff33));
+ allKeys.add(new MetaKeyBase("Hangul_Hanja", 0xff34));
+ allKeys.add(new MetaKeyBase("Kana_Shift", 0xff2e));
+ allKeys.add(new MetaKeyBase("Right_Alt", 0xffea));
+
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_LEFT,"Mouse Left"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_MIDDLE,"Mouse Middle"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_RIGHT,"Mouse Right"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_SCROLL_DOWN, "Mouse Scroll Down"));
+ allKeys.add(new MetaKeyBase(VncCanvas.MOUSE_BUTTON_SCROLL_UP, "Mouse Scroll Up"));
+
+ allKeys.add(new MetaKeyBase("Home", 0xFF50));
+ allKeys.add(new MetaKeyBase("Arrow Left", 0xFF51));
+ allKeys.add(new MetaKeyBase("Arrow Up", 0xFF52));
+ allKeys.add(new MetaKeyBase("Arrow Right", 0xFF53));
+ allKeys.add(new MetaKeyBase("Arrow Down", 0xFF54));
+ allKeys.add(new MetaKeyBase("Page Up", 0xFF55));
+ allKeys.add(new MetaKeyBase("Page Down", 0xFF56));
+ allKeys.add(new MetaKeyBase("End", 0xFF57));
+ allKeys.add(new MetaKeyBase("Insert", 0xFF63));
+ allKeys.add(new MetaKeyBase("Delete", 0xFFFF, KeyEvent.KEYCODE_DEL));
+ allKeys.add(new MetaKeyBase("Num Lock", 0xFF7F));
+ allKeys.add(new MetaKeyBase("Break", 0xFF6b));
+ allKeys.add(new MetaKeyBase("Scroll Lock", 0xFF14));
+ allKeys.add(new MetaKeyBase("Print Scrn", 0xFF15));
+ allKeys.add(new MetaKeyBase("Escape", 0xFF1B));
+ allKeys.add(new MetaKeyBase("Enter", 0xFF0D, KeyEvent.KEYCODE_ENTER));
+ allKeys.add(new MetaKeyBase("Tab", 0xFF09, KeyEvent.KEYCODE_TAB));
+ allKeys.add(new MetaKeyBase("BackSpace", 0xFF08));
+ allKeys.add(new MetaKeyBase("Space", 0x020, KeyEvent.KEYCODE_SPACE));
+
+ StringBuilder sb = new StringBuilder(" ");
+ for (int i=0; i<26; i++)
+ {
+ sb.setCharAt(0, (char)('A' + i));
+ allKeys.add(new MetaKeyBase(sb.toString(), 'a' + i, KeyEvent.KEYCODE_A + i));
+ }
+
+ for (int i=0; i<10; i++)
+ {
+ sb.setCharAt(0, (char)('0' + i));
+ allKeys.add(new MetaKeyBase(sb.toString(), '0' + i, KeyEvent.KEYCODE_0 + i));
+ }
+
+ for (int i=0; i<12; i++)
+ {
+ sb.setLength(0);
+ sb.append('F');
+ if (i<9)
+ sb.append(' ');
+ sb.append(Integer.toString(i+1));
+ allKeys.add(new MetaKeyBase(sb.toString(), 0xFFBE + i));
+ }
+
+ java.util.Collections.sort(allKeys);
+ allKeysNames = new String[allKeys.size()];
+ keysByKeyCode = new HashMap();
+ keysByMouseButton = new HashMap();
+ keysByKeySym = new HashMap();
+ for (int i=0; i() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.dbimpl.NewInstance#get()
+ */
+ @Override
+ public MetaKeyBean get() {
+ return new MetaKeyBean();
+ }
+ };
+ keyCtrlAltDel = new MetaKeyBean(0,VncCanvas.CTRL_MASK|VncCanvas.ALT_MASK,keysByKeyCode.get(KeyEvent.KEYCODE_DEL));
+ keyArrowLeft = new MetaKeyBean(0,0,keysByKeySym.get(0xFF51));
+ keyArrowUp = new MetaKeyBean(0,0,keysByKeySym.get(0xFF52));
+ keyArrowRight = new MetaKeyBean(0,0,keysByKeySym.get(0xFF53));
+ keyArrowDown = new MetaKeyBean(0,0,keysByKeySym.get(0xFF54));
+ }
+
+ private boolean _regenDesc;
+
+ MetaKeyBean()
+ {
+ }
+
+ MetaKeyBean(MetaKeyBean toCopy)
+ {
+ _regenDesc = true;
+ if (toCopy.isMouseClick())
+ setMouseButtons(toCopy.getMouseButtons());
+ else
+ setKeySym(toCopy.getKeySym());
+ setMetaListId(toCopy.getMetaListId());
+ setMetaFlags(toCopy.getMetaFlags());
+ }
+
+ MetaKeyBean(long listId, int metaFlags, MetaKeyBase base)
+ {
+ setMetaListId(listId);
+ setKeyBase(base);
+ setMetaFlags(metaFlags);
+ _regenDesc = true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractMetaKeyBean#getKeyDesc()
+ */
+ @Override
+ public String getKeyDesc() {
+ if (_regenDesc)
+ {
+ synchronized(this)
+ {
+ if (_regenDesc)
+ {
+ StringBuilder sb=new StringBuilder();
+ int meta=getMetaFlags();
+ if (0 != (meta & VncCanvas.SHIFT_MASK))
+ {
+ sb.append("Shift");
+ }
+ if (0 != (meta & VncCanvas.CTRL_MASK))
+ {
+ if (sb.length()>0)
+ sb.append('-');
+ sb.append("Ctrl");
+ }
+ if (0 != (meta & VncCanvas.ALT_MASK))
+ {
+ if (sb.length()>0)
+ sb.append('-');
+ sb.append("Alt");
+ }
+ if (sb.length()>0)
+ sb.append(' ');
+ MetaKeyBase base;
+ if (isMouseClick())
+ base=keysByMouseButton.get(getMouseButtons());
+ else
+ base=keysByKeySym.get(getKeySym());
+ sb.append(base.name);
+ setKeyDesc(sb.toString());
+ }
+ }
+ }
+ return super.getKeyDesc();
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractMetaKeyBean#setKeyDesc(java.lang.String)
+ */
+ @Override
+ public void setKeyDesc(String arg_keyDesc) {
+ super.setKeyDesc(arg_keyDesc);
+ _regenDesc = false;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractMetaKeyBean#setKeySym(int)
+ */
+ @Override
+ public void setKeySym(int arg_keySym) {
+ if (arg_keySym!=getKeySym() || isMouseClick())
+ {
+ setMouseClick(false);
+ _regenDesc=true;
+ super.setKeySym(arg_keySym);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractMetaKeyBean#setMetaFlags(int)
+ */
+ @Override
+ public void setMetaFlags(int arg_metaFlags) {
+ if (arg_metaFlags != getMetaFlags())
+ {
+ _regenDesc = true;
+ super.setMetaFlags(arg_metaFlags);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractMetaKeyBean#setMouseButtons(int)
+ */
+ @Override
+ public void setMouseButtons(int arg_mouseButtons) {
+ if (arg_mouseButtons!=getMouseButtons() || ! isMouseClick())
+ {
+ setMouseClick(true);
+ _regenDesc = true;
+ super.setMouseButtons(arg_mouseButtons);
+ }
+ }
+
+ void setKeyBase(MetaKeyBase base)
+ {
+ if (base.isMouse)
+ {
+ setMouseButtons(base.mouseButtons);
+ }
+ else
+ {
+ setKeySym(base.keySym);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MetaKeyBean)
+ {
+ return getKeyDesc().equals(((MetaKeyBean)o).getKeyDesc());
+ }
+ return false;
+ }
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(MetaKeyBean another) {
+ return getKeyDesc().compareTo(another.getKeyDesc());
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/MetaKeyDialog.java b/app/src/main/java/android/androidVNC/MetaKeyDialog.java
new file mode 100644
index 000000000..08437dfb2
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MetaKeyDialog.java
@@ -0,0 +1,602 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.app.*;
+import android.content.*;
+import android.database.*;
+import android.database.sqlite.*;
+import android.os.*;
+import android.view.*;
+import android.widget.*;
+import android.widget.AdapterView.*;
+import android.widget.CompoundButton.*;
+import java.text.*;
+import java.util.*;
+import java.util.Map.*;
+import net.kdt.pojavlaunch.*;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class MetaKeyDialog extends Dialog implements ConnectionSettable {
+
+ CheckBox _checkShift;
+ CheckBox _checkCtrl;
+ CheckBox _checkAlt;
+ TextView _textKeyDesc;
+ EditText _textListName;
+ Spinner _spinnerKeySelect;
+ Spinner _spinnerKeysInList;
+ Spinner _spinnerLists;
+
+ VncDatabase _database;
+ static ArrayList _lists;
+ ArrayList _keysInList;
+ long _listId;
+ VncCanvasActivity _canvasActivity;
+ MetaKeyBean _currentKeyBean;
+
+ static final String[] EMPTY_ARGS = new String[0];
+
+ ConnectionBean _connection;
+
+ /**
+ * @param context
+ */
+ public MetaKeyDialog(Context context) {
+ super(context);
+ setOwnerActivity((Activity)context);
+ _canvasActivity = (VncCanvasActivity)context;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ _canvasActivity.getMenuInflater().inflate(R.menu.metakeymenu, menu);
+ menu.findItem(R.id.itemDeleteKeyList).setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.MenuItem.OnMenuItemClickListener#onMenuItemClick(android.view.MenuItem)
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ Utils.showYesNoPrompt(_canvasActivity, "Delete key list",
+ "Delete list "+_textListName.getText().toString(),
+ new OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.content.DialogInterface.OnClickListener#onClick(android.content.DialogInterface, int)
+ */
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int position = _spinnerLists.getSelectedItemPosition();
+ if (position == Spinner.INVALID_POSITION)
+ return;
+ _listId = _lists.get(position).get_Id();
+ if (_listId > 1)
+ {
+ _lists.remove(position);
+ ArrayAdapter adapter = getSpinnerAdapter(_spinnerLists);
+ adapter.remove(adapter.getItem(position));
+ SQLiteDatabase db = _database.getWritableDatabase();
+ db.execSQL(MessageFormat.format("DELETE FROM {0} WHERE {1} = {2}",
+ MetaKeyBean.GEN_TABLE_NAME, MetaKeyBean.GEN_FIELD_METALISTID,
+ _listId));
+ db.execSQL(MessageFormat.format("DELETE FROM {0} WHERE {1} = {2}",
+ MetaList.GEN_TABLE_NAME, MetaList.GEN_FIELD__ID,
+ _listId));
+ _connection.setMetaListId(1);
+ _connection.save(db);
+ setMetaKeyList();
+ }
+ }
+ },
+ null);
+ return true;
+ }
+
+ });
+ menu.findItem(R.id.itemDeleteKey).setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.MenuItem.OnMenuItemClickListener#onMenuItemClick(android.view.MenuItem)
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ final int position = _spinnerKeysInList.getSelectedItemPosition();
+ if (position != Spinner.INVALID_POSITION)
+ {
+ final MetaKeyBean toDelete = _keysInList.get(position);
+ Utils.showYesNoPrompt(_canvasActivity, "Delete from list",
+ "Delete key " + toDelete.getKeyDesc(),
+ new OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.content.DialogInterface.OnClickListener#onClick(android.content.DialogInterface, int)
+ */
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getSpinnerAdapter(_spinnerKeysInList).remove(toDelete.getKeyDesc());
+ _keysInList.remove(position);
+ SQLiteDatabase db = _database.getWritableDatabase();
+ db.execSQL(
+ MessageFormat.format("DELETE FROM {0} WHERE {1} = {2}",
+ MetaKeyBean.GEN_TABLE_NAME, MetaKeyBean.GEN_FIELD_METALISTID,
+ toDelete.get_Id())
+ );
+ if (_connection.getLastMetaKeyId() == toDelete.get_Id())
+ {
+ _connection.setLastMetaKeyId(0);
+ _connection.save(db);
+ }
+ int newPos = _spinnerKeysInList.getSelectedItemPosition();
+ if (newPos != Spinner.INVALID_POSITION && newPos < _keysInList.size())
+ {
+ _currentKeyBean = new MetaKeyBean(_keysInList.get(newPos));
+ updateDialogForCurrentKey();
+ }
+ }
+ },
+ null);
+ }
+ return true;
+ }
+
+ });
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onMenuOpened(int, android.view.Menu)
+ */
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.itemDeleteKeyList).setEnabled(_currentKeyBean.getMetaListId()>1);
+ menu.findItem(R.id.itemDeleteKey).setEnabled(_spinnerKeysInList.getSelectedItemPosition()!=Spinner.INVALID_POSITION);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.metakey);
+ setTitle(R.string.meta_key_title);
+ _checkShift = (CheckBox)findViewById(R.id.checkboxShift);
+ _checkCtrl = (CheckBox)findViewById(R.id.checkboxCtrl);
+ _checkAlt = (CheckBox)findViewById(R.id.checkboxAlt);
+ _textKeyDesc = (TextView)findViewById(R.id.textKeyDesc);
+ _textListName = (EditText)findViewById(R.id.textListName);
+ _spinnerKeySelect = (Spinner)findViewById(R.id.spinnerKeySelect);
+ _spinnerKeysInList = (Spinner)findViewById(R.id.spinnerKeysInList);
+ _spinnerLists = (Spinner)findViewById(R.id.spinnerLists);
+
+ _database = _canvasActivity.database;
+ if (_lists == null) {
+ _lists = new ArrayList();
+ MetaList.getAll(_database.getReadableDatabase(), MetaList.GEN_TABLE_NAME, _lists, MetaList.GEN_NEW);
+ }
+ _spinnerKeySelect.setAdapter(new ArrayAdapter(getOwnerActivity(), android.R.layout.simple_spinner_item, MetaKeyBean.allKeysNames));
+ _spinnerKeySelect.setSelection(0);
+
+ setListSpinner();
+
+ _checkShift.setOnCheckedChangeListener(new MetaCheckListener(VncCanvas.SHIFT_MASK));
+ _checkAlt.setOnCheckedChangeListener(new MetaCheckListener(VncCanvas.ALT_MASK));
+ _checkCtrl.setOnCheckedChangeListener(new MetaCheckListener(VncCanvas.CTRL_MASK));
+
+ _spinnerLists.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemSelectedListener#onItemSelected(android.widget.AdapterView, android.view.View, int, long)
+ */
+ public void onItemSelected(AdapterView> parent, View view,
+ int position, long id) {
+ _connection.setMetaListId(_lists.get(position).get_Id());
+ _connection.Gen_update(_database.getWritableDatabase());
+ setMetaKeyList();
+ }
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemSelectedListener#onNothingSelected(android.widget.AdapterView)
+ */
+ public void onNothingSelected(AdapterView> parent) {
+ }
+
+ });
+
+ _spinnerKeysInList.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemSelectedListener#onItemSelected(android.widget.AdapterView, android.view.View, int, long)
+ */
+ public void onItemSelected(AdapterView> parent, View view,
+ int position, long id) {
+ _currentKeyBean = new MetaKeyBean(_keysInList.get(position));
+ updateDialogForCurrentKey();
+ }
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemSelectedListener#onNothingSelected(android.widget.AdapterView)
+ */
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ });
+
+ _spinnerKeySelect.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemSelectedListener#onItemSelected(android.widget.AdapterView, android.view.View, int, long)
+ */
+ public void onItemSelected(AdapterView> parent, View view,
+ int position, long id) {
+ if (_currentKeyBean == null) {
+ _currentKeyBean = new MetaKeyBean(0,0,MetaKeyBean.allKeys.get(position));
+ }
+ else {
+ _currentKeyBean.setKeyBase(MetaKeyBean.allKeys.get(position));
+ }
+ updateDialogForCurrentKey();
+ }
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemSelectedListener#onNothingSelected(android.widget.AdapterView)
+ */
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ });
+
+ ((Button)findViewById(R.id.buttonSend)).setOnClickListener(new View.OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ public void onClick(View v) {
+ sendCurrentKey();
+ dismiss();
+ }
+
+ });
+
+ ((Button)findViewById(R.id.buttonNewList)).setOnClickListener(new View.OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ MetaList newList = new MetaList();
+ newList.setName("New");
+ SQLiteDatabase db = _database.getWritableDatabase();
+ newList.Gen_insert(db);
+ _connection.setMetaListId(newList.get_Id());
+ _connection.save(db);
+ _lists.add(newList);
+ getSpinnerAdapter(_spinnerLists).add(newList.getName());
+ setMetaKeyList();
+ }
+
+ });
+ ((Button)findViewById(R.id.buttonCopyList)).setOnClickListener(new View.OnClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ MetaList newList = new MetaList();
+ newList.setName("Copy of " + _textListName.getText().toString());
+ SQLiteDatabase db = _database.getWritableDatabase();
+ newList.Gen_insert(db);
+ db.execSQL(MessageFormat.format(getCopyListString(), newList.get_Id(), _listId));
+ _connection.setMetaListId(newList.get_Id());
+ _connection.save(db);
+ _lists.add(newList);
+ getSpinnerAdapter(_spinnerLists).add(newList.getName());
+ setMetaKeyList();
+ }
+
+ });
+ }
+
+ private static String copyListString;
+
+ private String getCopyListString()
+ {
+ if (copyListString==null)
+ {
+ StringBuilder sb = new StringBuilder("INSERT INTO ");
+ sb.append(MetaKeyBean.GEN_TABLE_NAME);
+ sb.append(" ( ");
+ sb.append(MetaKeyBean.GEN_FIELD_METALISTID);
+ StringBuilder fieldList = new StringBuilder();
+ for (Entry s : _currentKeyBean.Gen_getValues().valueSet())
+ {
+ if (!s.getKey().equals(MetaKeyBean.GEN_FIELD__ID) && !s.getKey().equals(MetaKeyBean.GEN_FIELD_METALISTID)) {
+ fieldList.append(',');
+ fieldList.append(s.getKey());
+ }
+ }
+ String fl = fieldList.toString();
+ sb.append(fl);
+ sb.append(" ) SELECT {0} ");
+ sb.append(fl);
+ sb.append(" FROM ");
+ sb.append(MetaKeyBean.GEN_TABLE_NAME);
+ sb.append(" WHERE ");
+ sb.append(MetaKeyBean.GEN_FIELD_METALISTID);
+ sb.append(" = {1}");
+ copyListString = sb.toString();
+ }
+ return copyListString;
+ }
+
+ private boolean _justStarted;
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onStart()
+ */
+ @Override
+ protected void onStart() {
+ takeKeyEvents(true);
+ _justStarted = true;
+ super.onStart();
+ View v = getCurrentFocus();
+ if (v!=null)
+ v.clearFocus();
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onStop()
+ */
+ @Override
+ protected void onStop() {
+ int i = 0;
+ for (MetaList l : _lists)
+ {
+ if (l.get_Id() == _listId)
+ {
+ String s = _textListName.getText().toString();
+ if (! s.equals(l.getName()))
+ {
+ l.setName(s);
+ l.Gen_update(_database.getWritableDatabase());
+ ArrayAdapter adapter = getSpinnerAdapter(_spinnerLists);
+ adapter.remove(adapter.getItem(i));
+ adapter.insert(s,i);
+ }
+ break;
+ }
+ i++;
+ }
+ takeKeyEvents(false);
+ super.onStop();
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onKeyDown(int, android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ _justStarted = false;
+ if (keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_MENU && getCurrentFocus() == null)
+ {
+ int flags = event.getMetaState();
+ int currentFlags = _currentKeyBean.getMetaFlags();
+ MetaKeyBase base = MetaKeyBean.keysByKeyCode.get(keyCode);
+ if (base != null)
+ {
+ if (0 != (flags & KeyEvent.META_SHIFT_ON))
+ {
+ currentFlags |= VncCanvas.SHIFT_MASK;
+ }
+ if (0 != (flags & KeyEvent.META_ALT_ON))
+ {
+ currentFlags |= VncCanvas.ALT_MASK;
+ }
+ _currentKeyBean.setKeyBase(base);
+ }
+ else
+ {
+ // Toggle flags according to meta keys
+ if (0 != (flags & KeyEvent.META_SHIFT_ON))
+ {
+ currentFlags ^= VncCanvas.SHIFT_MASK;
+ }
+ if (0 != (flags & KeyEvent.META_ALT_ON))
+ {
+ currentFlags ^= VncCanvas.ALT_MASK;
+ }
+ if (keyCode == KeyEvent.KEYCODE_SEARCH)
+ {
+ currentFlags ^= VncCanvas.CTRL_MASK;
+ }
+ }
+ _currentKeyBean.setMetaFlags(currentFlags);
+ updateDialogForCurrentKey();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onKeyUp(int, android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (! _justStarted && keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_MENU && getCurrentFocus()==null)
+ {
+ if (MetaKeyBean.keysByKeyCode.get(keyCode) != null)
+ {
+ sendCurrentKey();
+ dismiss();
+ }
+ return true;
+ }
+ _justStarted = false;
+ return super.onKeyUp(keyCode, event);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private static ArrayAdapter getSpinnerAdapter(Spinner spinner)
+ {
+ return (ArrayAdapter)spinner.getAdapter();
+ }
+
+ void sendCurrentKey()
+ {
+ int index = Collections.binarySearch(_keysInList, _currentKeyBean);
+ SQLiteDatabase db = _database.getWritableDatabase();
+ if (index < 0)
+ {
+ int insertionPoint = -(index + 1);
+ _currentKeyBean.Gen_insert(db);
+ _keysInList.add(insertionPoint,_currentKeyBean);
+ getSpinnerAdapter(_spinnerKeysInList).insert(_currentKeyBean.getKeyDesc(), insertionPoint);
+ _spinnerKeysInList.setSelection(insertionPoint);
+ _connection.setLastMetaKeyId(_currentKeyBean.get_Id());
+ }
+ else
+ {
+ MetaKeyBean bean = _keysInList.get(index);
+ _connection.setLastMetaKeyId(bean.get_Id());
+ _spinnerKeysInList.setSelection(index);
+ }
+ _connection.Gen_update(db);
+ _canvasActivity.vncCanvas.sendMetaKey(_currentKeyBean);
+ }
+
+ void setMetaKeyList()
+ {
+ long listId = _connection.getMetaListId();
+ if (listId!=_listId) {
+ for (int i=0; i<_lists.size(); ++i)
+ {
+ MetaList list = _lists.get(i);
+ if (list.get_Id()==listId)
+ {
+ _spinnerLists.setSelection(i);
+ _keysInList = new ArrayList();
+ Cursor c = _database.getReadableDatabase().rawQuery(
+ MessageFormat.format("SELECT * FROM {0} WHERE {1} = {2} ORDER BY KEYDESC",
+ MetaKeyBean.GEN_TABLE_NAME,
+ MetaKeyBean.GEN_FIELD_METALISTID,
+ listId),
+ EMPTY_ARGS);
+ MetaKeyBean.Gen_populateFromCursor(
+ c,
+ _keysInList,
+ MetaKeyBean.NEW);
+ c.close();
+ ArrayList keys = new ArrayList(_keysInList.size());
+ int selectedOffset = 0;
+ long lastSelectedKeyId = _canvasActivity.getConnection().getLastMetaKeyId();
+ for (int j=0; j<_keysInList.size(); j++)
+ {
+ MetaKeyBean key = _keysInList.get(j);
+ keys.add( key.getKeyDesc());
+ if (lastSelectedKeyId==key.get_Id())
+ {
+ selectedOffset = j;
+ }
+ }
+ _spinnerKeysInList.setAdapter(new ArrayAdapter(getOwnerActivity(), android.R.layout.simple_spinner_item, keys));
+ if (keys.size()>0)
+ {
+ _spinnerKeysInList.setSelection(selectedOffset);
+ _currentKeyBean = new MetaKeyBean(_keysInList.get(selectedOffset));
+ }
+ else
+ {
+ _currentKeyBean = new MetaKeyBean(listId, 0, MetaKeyBean.allKeys.get(0));
+ }
+ updateDialogForCurrentKey();
+ _textListName.setText(list.getName());
+ break;
+ }
+ }
+ _listId = listId;
+ }
+ }
+
+ private void updateDialogForCurrentKey()
+ {
+ int flags = _currentKeyBean.getMetaFlags();
+ _checkAlt.setChecked(0 != (flags & VncCanvas.ALT_MASK));
+ _checkShift.setChecked(0 != (flags & VncCanvas.SHIFT_MASK));
+ _checkCtrl.setChecked(0 != (flags & VncCanvas.CTRL_MASK));
+ MetaKeyBase base = null;
+ if (_currentKeyBean.isMouseClick())
+ {
+ base = MetaKeyBean.keysByMouseButton.get(_currentKeyBean.getMouseButtons());
+ } else {
+ base = MetaKeyBean.keysByKeySym.get(_currentKeyBean.getKeySym());
+ }
+ if (base != null) {
+ int index = Collections.binarySearch(MetaKeyBean.allKeys,base);
+ if (index >= 0) {
+ _spinnerKeySelect.setSelection(index);
+ }
+ }
+ _textKeyDesc.setText(_currentKeyBean.getKeyDesc());
+ }
+
+ public void setConnection(ConnectionBean conn)
+ {
+ if ( _connection != conn) {
+ _connection = conn;
+ setMetaKeyList();
+ }
+ }
+
+ void setListSpinner()
+ {
+ ArrayList listNames = new ArrayList(_lists.size());
+ for (int i=0; i<_lists.size(); ++i)
+ {
+ MetaList l = _lists.get(i);
+ listNames.add(l.getName());
+ }
+ _spinnerLists.setAdapter(new ArrayAdapter(getOwnerActivity(),android.R.layout.simple_spinner_item, listNames));
+ }
+
+ /**
+ * @author Michael A. MacDonald
+ *
+ */
+ class MetaCheckListener implements OnCheckedChangeListener {
+
+ private int _mask;
+
+ MetaCheckListener(int mask) {
+ _mask = mask;
+ }
+ /* (non-Javadoc)
+ * @see android.widget.CompoundButton.OnCheckedChangeListener#onCheckedChanged(android.widget.CompoundButton, boolean)
+ */
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ if (isChecked)
+ {
+ _currentKeyBean.setMetaFlags(_currentKeyBean.getMetaFlags() | _mask);
+ }
+ else
+ {
+ _currentKeyBean.setMetaFlags(_currentKeyBean.getMetaFlags() & ~_mask);
+ }
+ _textKeyDesc.setText(_currentKeyBean.getKeyDesc());
+ }
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/MetaList.java b/app/src/main/java/android/androidVNC/MetaList.java
new file mode 100644
index 000000000..609c1240c
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MetaList.java
@@ -0,0 +1,84 @@
+// This class was generated from android.androidVNC.IMetaList by a tool
+// Do not edit this file directly! PLX THX
+package android.androidVNC;
+
+public class MetaList extends com.antlersoft.android.dbimpl.IdImplementationBase implements IMetaList {
+
+ public static final String GEN_TABLE_NAME = "META_LIST";
+ public static final int GEN_COUNT = 2;
+
+ // Field constants
+ public static final String GEN_FIELD__ID = "_id";
+ public static final int GEN_ID__ID = 0;
+ public static final String GEN_FIELD_NAME = "NAME";
+ public static final int GEN_ID_NAME = 1;
+
+ // SQL Command for creating the table
+ public static String GEN_CREATE = "CREATE TABLE META_LIST (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "NAME TEXT" +
+ ")";
+
+ // Members corresponding to defined fields
+ private long gen__Id;
+ private java.lang.String gen_name;
+
+
+ public static final com.antlersoft.android.dbimpl.NewInstance GEN_NEW = new com.antlersoft.android.dbimpl.NewInstance() {
+ public MetaList get() {
+ return new MetaList();
+ }
+ }
+ ;
+
+ public String Gen_tableName() { return GEN_TABLE_NAME; }
+
+ // Field accessors
+ public long get_Id() { return gen__Id; }
+ public void set_Id(long arg__Id) { gen__Id = arg__Id; }
+ public java.lang.String getName() { return gen_name; }
+ public void setName(java.lang.String arg_name) { gen_name = arg_name; }
+
+ public android.content.ContentValues Gen_getValues() {
+ android.content.ContentValues values=new android.content.ContentValues();
+ values.put(GEN_FIELD__ID,Long.toString(this.gen__Id));
+ values.put(GEN_FIELD_NAME,this.gen_name);
+ return values;
+ }
+
+ /**
+ * Return an array that gives the column index in the cursor for each field defined
+ * @param cursor Database cursor over some columns, possibly including this table
+ * @return array of column indices; -1 if the column with that id is not in cursor
+ */
+ public int[] Gen_columnIndices(android.database.Cursor cursor) {
+ int[] result=new int[GEN_COUNT];
+ result[0] = cursor.getColumnIndex(GEN_FIELD__ID);
+ // Make compatible with database generated by older version of plugin with uppercase column name
+ if (result[0] == -1) {
+ result[0] = cursor.getColumnIndex("_ID");
+ }
+ result[1] = cursor.getColumnIndex(GEN_FIELD_NAME);
+ return result;
+ }
+
+ /**
+ * Populate one instance from a cursor
+ */
+ public void Gen_populate(android.database.Cursor cursor,int[] columnIndices) {
+ if ( columnIndices[GEN_ID__ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID__ID])) {
+ gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
+ }
+ if ( columnIndices[GEN_ID_NAME] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_NAME])) {
+ gen_name = cursor.getString(columnIndices[GEN_ID_NAME]);
+ }
+ }
+
+ /**
+ * Populate one instance from a ContentValues
+ */
+ public void Gen_populate(android.content.ContentValues values) {
+ gen__Id = values.getAsLong(GEN_FIELD__ID);
+ gen_name = values.getAsString(GEN_FIELD_NAME);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/MostRecentBean.java b/app/src/main/java/android/androidVNC/MostRecentBean.java
new file mode 100644
index 000000000..75968e92a
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MostRecentBean.java
@@ -0,0 +1,108 @@
+// This class was generated from android.androidVNC.IMostRecentBean by a tool
+// Do not edit this file directly! PLX THX
+package android.androidVNC;
+
+public class MostRecentBean extends com.antlersoft.android.dbimpl.IdImplementationBase implements IMostRecentBean {
+
+ public static final String GEN_TABLE_NAME = "MOST_RECENT";
+ public static final int GEN_COUNT = 4;
+
+ // Field constants
+ public static final String GEN_FIELD__ID = "_id";
+ public static final int GEN_ID__ID = 0;
+ public static final String GEN_FIELD_CONNECTION_ID = "CONNECTION_ID";
+ public static final int GEN_ID_CONNECTION_ID = 1;
+ public static final String GEN_FIELD_SHOW_SPLASH_VERSION = "SHOW_SPLASH_VERSION";
+ public static final int GEN_ID_SHOW_SPLASH_VERSION = 2;
+ public static final String GEN_FIELD_TEXT_INDEX = "TEXT_INDEX";
+ public static final int GEN_ID_TEXT_INDEX = 3;
+
+ // SQL Command for creating the table
+ public static String GEN_CREATE = "CREATE TABLE MOST_RECENT (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "CONNECTION_ID INTEGER," +
+ "SHOW_SPLASH_VERSION INTEGER," +
+ "TEXT_INDEX INTEGER" +
+ ")";
+
+ // Members corresponding to defined fields
+ private long gen__Id;
+ private long gen_CONNECTION_ID;
+ private long gen_SHOW_SPLASH_VERSION;
+ private long gen_TEXT_INDEX;
+
+
+ public static final com.antlersoft.android.dbimpl.NewInstance GEN_NEW = new com.antlersoft.android.dbimpl.NewInstance() {
+ public MostRecentBean get() {
+ return new MostRecentBean();
+ }
+ }
+ ;
+
+ public String Gen_tableName() { return GEN_TABLE_NAME; }
+
+ // Field accessors
+ public long get_Id() { return gen__Id; }
+ public void set_Id(long arg__Id) { gen__Id = arg__Id; }
+ public long getConnectionId() { return gen_CONNECTION_ID; }
+ public void setConnectionId(long arg_CONNECTION_ID) { gen_CONNECTION_ID = arg_CONNECTION_ID; }
+ public long getShowSplashVersion() { return gen_SHOW_SPLASH_VERSION; }
+ public void setShowSplashVersion(long arg_SHOW_SPLASH_VERSION) { gen_SHOW_SPLASH_VERSION = arg_SHOW_SPLASH_VERSION; }
+ public long getTextIndex() { return gen_TEXT_INDEX; }
+ public void setTextIndex(long arg_TEXT_INDEX) { gen_TEXT_INDEX = arg_TEXT_INDEX; }
+
+ public android.content.ContentValues Gen_getValues() {
+ android.content.ContentValues values=new android.content.ContentValues();
+ values.put(GEN_FIELD__ID,Long.toString(this.gen__Id));
+ values.put(GEN_FIELD_CONNECTION_ID,Long.toString(this.gen_CONNECTION_ID));
+ values.put(GEN_FIELD_SHOW_SPLASH_VERSION,Long.toString(this.gen_SHOW_SPLASH_VERSION));
+ values.put(GEN_FIELD_TEXT_INDEX,Long.toString(this.gen_TEXT_INDEX));
+ return values;
+ }
+
+ /**
+ * Return an array that gives the column index in the cursor for each field defined
+ * @param cursor Database cursor over some columns, possibly including this table
+ * @return array of column indices; -1 if the column with that id is not in cursor
+ */
+ public int[] Gen_columnIndices(android.database.Cursor cursor) {
+ int[] result=new int[GEN_COUNT];
+ result[0] = cursor.getColumnIndex(GEN_FIELD__ID);
+ // Make compatible with database generated by older version of plugin with uppercase column name
+ if (result[0] == -1) {
+ result[0] = cursor.getColumnIndex("_ID");
+ }
+ result[1] = cursor.getColumnIndex(GEN_FIELD_CONNECTION_ID);
+ result[2] = cursor.getColumnIndex(GEN_FIELD_SHOW_SPLASH_VERSION);
+ result[3] = cursor.getColumnIndex(GEN_FIELD_TEXT_INDEX);
+ return result;
+ }
+
+ /**
+ * Populate one instance from a cursor
+ */
+ public void Gen_populate(android.database.Cursor cursor,int[] columnIndices) {
+ if ( columnIndices[GEN_ID__ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID__ID])) {
+ gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
+ }
+ if ( columnIndices[GEN_ID_CONNECTION_ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_CONNECTION_ID])) {
+ gen_CONNECTION_ID = cursor.getLong(columnIndices[GEN_ID_CONNECTION_ID]);
+ }
+ if ( columnIndices[GEN_ID_SHOW_SPLASH_VERSION] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_SHOW_SPLASH_VERSION])) {
+ gen_SHOW_SPLASH_VERSION = cursor.getLong(columnIndices[GEN_ID_SHOW_SPLASH_VERSION]);
+ }
+ if ( columnIndices[GEN_ID_TEXT_INDEX] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_TEXT_INDEX])) {
+ gen_TEXT_INDEX = cursor.getLong(columnIndices[GEN_ID_TEXT_INDEX]);
+ }
+ }
+
+ /**
+ * Populate one instance from a ContentValues
+ */
+ public void Gen_populate(android.content.ContentValues values) {
+ gen__Id = values.getAsLong(GEN_FIELD__ID);
+ gen_CONNECTION_ID = values.getAsLong(GEN_FIELD_CONNECTION_ID);
+ gen_SHOW_SPLASH_VERSION = values.getAsLong(GEN_FIELD_SHOW_SPLASH_VERSION);
+ gen_TEXT_INDEX = values.getAsLong(GEN_FIELD_TEXT_INDEX);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/MouseMover.java b/app/src/main/java/android/androidVNC/MouseMover.java
new file mode 100644
index 000000000..cff5e437e
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/MouseMover.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2010 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+/**
+ * Specialization of panner that moves the mouse instead of panning the screen
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+class MouseMover extends Panner {
+
+ public MouseMover(VncCanvasActivity act, Handler hand) {
+ super(act, hand);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ long interval = SystemClock.uptimeMillis() - lastSent;
+ lastSent += interval;
+ double scale = (double)interval / 50.0;
+ VncCanvas canvas = activity.vncCanvas;
+ //Log.v(TAG, String.format("panning %f %d %d", scale, (int)((double)velocity.x * scale), (int)((double)velocity.y * scale)));
+ if ( canvas.processPointerEvent((int)(canvas.mouseX + ((double)velocity.x * scale)), (int)(canvas.mouseY + ((double)velocity.y * scale)), MotionEvent.ACTION_MOVE, 0, false, false))
+ {
+ if (updater.updateVelocity(velocity, interval))
+ {
+ handler.postDelayed(this, 50);
+ }
+ else
+ {
+ //Log.v(TAG, "Updater requests stop");
+ stop();
+ }
+ }
+ else
+ {
+ //Log.v(TAG, "Panning failed");
+ stop();
+ }
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/OneToOneScaling.java b/app/src/main/java/android/androidVNC/OneToOneScaling.java
new file mode 100644
index 000000000..a45c707cb
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/OneToOneScaling.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.widget.ImageView.*;
+import net.kdt.pojavlaunch.*;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class OneToOneScaling extends AbstractScaling {
+
+ /**
+ * @param id
+ * @param scaleType
+ */
+ public OneToOneScaling() {
+ super(R.id.itemOneToOne,ScaleType.CENTER);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getDefaultHandlerId()
+ */
+ @Override
+ int getDefaultHandlerId() {
+ return R.id.itemInputTouchPanTrackballMouse;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isAbleToPan()
+ */
+ @Override
+ boolean isAbleToPan() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isValidInputMode(int)
+ */
+ @Override
+ boolean isValidInputMode(int mode) {
+ return mode != R.id.itemInputFitToScreen;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#setScaleTypeForActivity(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ void setScaleTypeForActivity(VncCanvasActivity activity) {
+ super.setScaleTypeForActivity(activity);
+ activity.vncCanvas.scrollToAbsolute();
+ activity.vncCanvas.pan(0,0);
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/Panner.java b/app/src/main/java/android/androidVNC/Panner.java
new file mode 100644
index 000000000..4298f1ad8
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/Panner.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.PointF;
+import android.os.Handler;
+import android.os.SystemClock;
+
+/**
+ * Handles panning the screen continuously over a period of time
+ * @author Michael A. MacDonald
+ */
+class Panner implements Runnable {
+
+ VncCanvasActivity activity;
+ Handler handler;
+ PointF velocity;
+ long lastSent;
+ VelocityUpdater updater;
+
+ private static final String TAG = "PANNER";
+
+ /**
+ * Specify how the panning velocity changes over time
+ * @author Michael A. MacDonald
+ */
+ interface VelocityUpdater {
+ /**
+ * Called approximately every 50 ms to update the velocity of panning
+ * @param p X and Y components to update
+ * @param interval Milliseconds since last update
+ * @return False if the panning should stop immediately; true otherwise
+ */
+ boolean updateVelocity(PointF p, long interval);
+ }
+
+ static class DefaultUpdater implements VelocityUpdater {
+
+ static DefaultUpdater instance = new DefaultUpdater();
+
+ /**
+ * Don't change velocity
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ return true;
+ }
+
+ }
+
+ Panner(VncCanvasActivity act, Handler hand) {
+ activity = act;
+ velocity = new PointF();
+ handler = hand;
+ }
+
+ void stop()
+ {
+ handler.removeCallbacks(this);
+ }
+
+ void start(float xv, float yv, VelocityUpdater update)
+ {
+ if (update == null)
+ update = DefaultUpdater.instance;
+ updater = update;
+ velocity.x = xv;
+ velocity.y = yv;
+ //Log.v(TAG, String.format("pan start %f %f", velocity.x, velocity.y));
+ lastSent = SystemClock.uptimeMillis();
+
+ handler.postDelayed(this, 50);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ long interval = SystemClock.uptimeMillis() - lastSent;
+ lastSent += interval;
+ double scale = (double)interval / 50.0;
+ //Log.v(TAG, String.format("panning %f %d %d", scale, (int)((double)velocity.x * scale), (int)((double)velocity.y * scale)));
+ if ( activity.vncCanvas.pan((int)((double)velocity.x * scale), (int)((double)velocity.y * scale)))
+ {
+ if (updater.updateVelocity(velocity, interval))
+ {
+ handler.postDelayed(this, 50);
+ }
+ else
+ {
+ //Log.v(TAG, "Updater requests stop");
+ stop();
+ }
+ }
+ else
+ {
+ //Log.v(TAG, "Panning failed");
+ stop();
+ }
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/RepeaterDialog.java b/app/src/main/java/android/androidVNC/RepeaterDialog.java
new file mode 100644
index 000000000..1db08fdcc
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/RepeaterDialog.java
@@ -0,0 +1,55 @@
+/**
+ *
+ */
+package android.androidVNC;
+
+import android.app.*;
+import android.os.*;
+import android.text.*;
+import android.view.*;
+import android.widget.*;
+import net.kdt.pojavlaunch.*;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class RepeaterDialog extends Dialog {
+ private EditText _repeaterId;
+ androidVNC _configurationDialog;
+
+ RepeaterDialog(androidVNC context) {
+ super(context);
+ setOwnerActivity((Activity)context);
+ _configurationDialog = context;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Dialog#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle(R.string.repeater_dialog_title);
+
+ setContentView(R.layout.repeater_dialog);
+ _repeaterId=(EditText)findViewById(R.id.textRepeaterInfo);
+ ((TextView)findViewById(R.id.textRepeaterCaption)).setText(Html.fromHtml(getContext().getString(R.string.repeater_caption)));
+ ((Button)findViewById(R.id.buttonSaveRepeater)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ _configurationDialog.updateRepeaterInfo(true, _repeaterId.getText().toString());
+ dismiss();
+ }
+ });
+ ((Button)findViewById(R.id.buttonClearRepeater)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ _configurationDialog.updateRepeaterInfo(false, "");
+ dismiss();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/RfbProto.java b/app/src/main/java/android/androidVNC/RfbProto.java
new file mode 100644
index 000000000..6b3087de3
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/RfbProto.java
@@ -0,0 +1,1303 @@
+//
+// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved.
+// Copyright (C) 2001-2006 Constantin Kaplinsky. All Rights Reserved.
+// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
+// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+//
+// This is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this software; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+// USA.
+//
+
+//
+// RfbProto.java
+//
+
+package android.androidVNC;
+
+import java.io.*;
+//- import java.awt.*;
+//- import java.awt.event.*;
+import java.net.Socket;
+//- import java.util.zip.*;
+import android.util.Log;
+
+/**
+ * Access the RFB protocol through a socket.
+ *
+ * This class has no knowledge of the android-specific UI; it sees framebuffer updates
+ * and input events as defined in the RFB protocol.
+ *
+ */
+class RfbProto {
+
+ final static String TAG = "RfbProto";
+
+ final static String
+ versionMsg_3_3 = "RFB 003.003\n",
+ versionMsg_3_7 = "RFB 003.007\n",
+ versionMsg_3_8 = "RFB 003.008\n";
+
+ // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC
+ final static String
+ StandardVendor = "STDV",
+ TridiaVncVendor = "TRDV",
+ TightVncVendor = "TGHT";
+
+ // Security types
+ final static int
+ SecTypeInvalid = 0,
+ SecTypeNone = 1,
+ SecTypeVncAuth = 2,
+ SecTypeTight = 16,
+ SecTypeUltra34 = 0xfffffffa;
+
+ // Supported tunneling types
+ final static int
+ NoTunneling = 0;
+ final static String
+ SigNoTunneling = "NOTUNNEL";
+
+ // Supported authentication types
+ final static int
+ AuthNone = 1,
+ AuthVNC = 2,
+ AuthUnixLogin = 129,
+ AuthUltra = 17;
+ final static String
+ SigAuthNone = "NOAUTH__",
+ SigAuthVNC = "VNCAUTH_",
+ SigAuthUnixLogin = "ULGNAUTH";
+
+ // VNC authentication results
+ final static int
+ VncAuthOK = 0,
+ VncAuthFailed = 1,
+ VncAuthTooMany = 2;
+
+ // Server-to-client messages
+ final static int
+ FramebufferUpdate = 0,
+ SetColourMapEntries = 1,
+ Bell = 2,
+ ServerCutText = 3,
+ TextChat = 11;
+
+ // Client-to-server messages
+ final static int
+ SetPixelFormat = 0,
+ FixColourMapEntries = 1,
+ SetEncodings = 2,
+ FramebufferUpdateRequest = 3,
+ KeyboardEvent = 4,
+ PointerEvent = 5,
+ ClientCutText = 6;
+
+ // Supported encodings and pseudo-encodings
+ final static int
+ EncodingRaw = 0,
+ EncodingCopyRect = 1,
+ EncodingRRE = 2,
+ EncodingCoRRE = 4,
+ EncodingHextile = 5,
+ EncodingZlib = 6,
+ EncodingTight = 7,
+ EncodingZRLE = 16,
+ EncodingCompressLevel0 = 0xFFFFFF00,
+ EncodingQualityLevel0 = 0xFFFFFFE0,
+ EncodingXCursor = 0xFFFFFF10,
+ EncodingRichCursor = 0xFFFFFF11,
+ EncodingPointerPos = 0xFFFFFF18,
+ EncodingLastRect = 0xFFFFFF20,
+ EncodingNewFBSize = 0xFFFFFF21;
+ final static String
+ SigEncodingRaw = "RAW_____",
+ SigEncodingCopyRect = "COPYRECT",
+ SigEncodingRRE = "RRE_____",
+ SigEncodingCoRRE = "CORRE___",
+ SigEncodingHextile = "HEXTILE_",
+ SigEncodingZlib = "ZLIB____",
+ SigEncodingTight = "TIGHT___",
+ SigEncodingZRLE = "ZRLE____",
+ SigEncodingCompressLevel0 = "COMPRLVL",
+ SigEncodingQualityLevel0 = "JPEGQLVL",
+ SigEncodingXCursor = "X11CURSR",
+ SigEncodingRichCursor = "RCHCURSR",
+ SigEncodingPointerPos = "POINTPOS",
+ SigEncodingLastRect = "LASTRECT",
+ SigEncodingNewFBSize = "NEWFBSIZ";
+
+ final static int MaxNormalEncoding = 255;
+
+ // Contstants used in the Hextile decoder
+ final static int
+ HextileRaw = 1,
+ HextileBackgroundSpecified = 2,
+ HextileForegroundSpecified = 4,
+ HextileAnySubrects = 8,
+ HextileSubrectsColoured = 16;
+
+ // Contstants used in the Tight decoder
+ final static int TightMinToCompress = 12;
+ final static int
+ TightExplicitFilter = 0x04,
+ TightFill = 0x08,
+ TightJpeg = 0x09,
+ TightMaxSubencoding = 0x09,
+ TightFilterCopy = 0x00,
+ TightFilterPalette = 0x01,
+ TightFilterGradient = 0x02;
+
+ // Constants used for UltraVNC chat extension
+ final static int
+ CHAT_OPEN = -1,
+ CHAT_CLOSE = -2,
+ CHAT_FINISHED = -3;
+
+ String host;
+ int port;
+ Socket sock;
+ DataInputStream is;
+ OutputStream os;
+
+ DH dh;
+ long dh_resp;
+
+ //- SessionRecorder rec;
+ boolean inNormalProtocol = false;
+ //- VncViewer viewer;
+
+ // Java on UNIX does not call keyPressed() on some keys, for example
+ // swedish keys To prevent our workaround to produce duplicate
+ // keypresses on JVMs that actually works, keep track of if
+ // keyPressed() for a "broken" key was called or not.
+ boolean brokenKeyPressed = false;
+
+ // This will be set to true on the first framebuffer update
+ // containing Zlib-, ZRLE- or Tight-encoded data.
+ boolean wereZlibUpdates = false;
+
+ // This will be set to false if the startSession() was called after
+ // we have received at least one Zlib-, ZRLE- or Tight-encoded
+ // framebuffer update.
+ boolean recordFromBeginning = true;
+
+ // This fields are needed to show warnings about inefficiently saved
+ // sessions only once per each saved session file.
+ boolean zlibWarningShown;
+ boolean tightWarningShown;
+
+ // Before starting to record each saved session, we set this field
+ // to 0, and increment on each framebuffer update. We don't flush
+ // the SessionRecorder data into the file before the second update.
+ // This allows us to write initial framebuffer update with zero
+ // timestamp, to let the player show initial desktop before
+ // playback.
+ int numUpdatesInSession;
+
+ // Measuring network throughput.
+ boolean timing;
+ long timeWaitedIn100us;
+ long timedKbits;
+
+ // Protocol version and TightVNC-specific protocol options.
+ int serverMajor, serverMinor;
+ int clientMajor, clientMinor;
+ boolean protocolTightVNC;
+ CapsContainer tunnelCaps, authCaps;
+ CapsContainer serverMsgCaps, clientMsgCaps;
+ CapsContainer encodingCaps;
+
+ // If true, informs that the RFB socket was closed.
+ private boolean closed;
+
+ //
+ // Constructor. Make TCP connection to RFB server.
+ //
+
+
+ //-RfbProto(String h, int p, VncViewer v) throws IOException {
+ RfbProto(String h, int p) throws IOException{
+ //- viewer = v;
+ host = h;
+ port = p;
+
+ /*
+ if (viewer.socketFactory == null) {
+ sock = new Socket(host, port);
+ } else {
+ try {
+ Class factoryClass = Class.forName(viewer.socketFactory);
+ SocketFactory factory = (SocketFactory)factoryClass.newInstance();
+ //- if (viewer.inAnApplet)
+ //- sock = factory.createSocket(host, port, viewer);
+ //- else
+ sock = factory.createSocket(host, port, viewer.mainArgs);
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ throw new IOException(e.getMessage());
+ }
+ } */
+ //+
+ sock = new Socket(host, port);
+ is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
+ 16384));
+ os = sock.getOutputStream();
+
+ timing = false;
+ timeWaitedIn100us = 5;
+ timedKbits = 0;
+ }
+
+
+ synchronized void close() {
+ try {
+ sock.close();
+ closed = true;
+ //- System.out.println("RFB socket closed");
+ Log.v(TAG, "RFB socket closed");
+ /*-
+ if (rec != null) {
+ rec.close();
+ rec = null;
+
+ } */
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ synchronized boolean closed() {
+ return closed;
+ }
+
+ //
+ // Read server's protocol version message
+ //
+
+ void readVersionMsg() throws Exception {
+
+ byte[] b = new byte[12];
+
+ readFully(b);
+
+ if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ')
+ || (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9')
+ || (b[6] < '0') || (b[6] > '9') || (b[7] != '.')
+ || (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9')
+ || (b[10] < '0') || (b[10] > '9') || (b[11] != '\n'))
+ {
+ Log.i(TAG,new String(b));
+ throw new Exception("Host " + host + " port " + port +
+ " is not an RFB server");
+ }
+
+ serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
+ serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0');
+
+ if (serverMajor < 3) {
+ throw new Exception("RFB server does not support protocol version 3");
+ }
+ }
+
+
+ //
+ // Write our protocol version message
+ //
+
+ synchronized void writeVersionMsg() throws IOException {
+ clientMajor = 3;
+ if (serverMajor > 3 || serverMinor >= 8) {
+ clientMinor = 8;
+ os.write(versionMsg_3_8.getBytes());
+ } else if (serverMinor >= 7) {
+ clientMinor = 7;
+ os.write(versionMsg_3_7.getBytes());
+ } else {
+ clientMinor = 3;
+ os.write(versionMsg_3_3.getBytes());
+ }
+ protocolTightVNC = false;
+ }
+
+
+ //
+ // Negotiate the authentication scheme.
+ //
+
+ int negotiateSecurity(int bitPref) throws Exception {
+ return (clientMinor >= 7) ?
+ selectSecurityType(bitPref) : readSecurityType(bitPref);
+ }
+
+ //
+ // Read security type from the server (protocol version 3.3).
+ //
+
+ int readSecurityType(int bitPref) throws Exception {
+ int secType = is.readInt();
+
+ switch (secType) {
+ case SecTypeInvalid:
+ readConnFailedReason();
+ return SecTypeInvalid; // should never be executed
+ case SecTypeNone:
+ case SecTypeVncAuth:
+ return secType;
+ case SecTypeUltra34:
+ if((bitPref & 1) == 1)
+ return secType;
+ throw new Exception("Username required.");
+ default:
+ throw new Exception("Unknown security type from RFB server: " + secType);
+ }
+ }
+
+ //
+ // Select security type from the server's list (protocol versions 3.7/3.8).
+ //
+
+ int selectSecurityType(int bitPref) throws Exception {
+ int secType = SecTypeInvalid;
+
+ // Read the list of security types.
+ int nSecTypes = is.readUnsignedByte();
+ if (nSecTypes == 0) {
+ readConnFailedReason();
+ return SecTypeInvalid; // should never be executed
+ }
+ byte[] secTypes = new byte[nSecTypes];
+ readFully(secTypes);
+
+ // Find out if the server supports TightVNC protocol extensions
+ for (int i = 0; i < nSecTypes; i++) {
+ if (secTypes[i] == SecTypeTight) {
+ protocolTightVNC = true;
+ os.write(SecTypeTight);
+ return SecTypeTight;
+ }
+ }
+
+ // Find first supported security type.
+ for (int i = 0; i < nSecTypes; i++) {
+ if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) {
+ secType = secTypes[i];
+ break;
+ }
+ }
+
+ if (secType == SecTypeInvalid) {
+ throw new Exception("Server did not offer supported security type");
+ } else {
+ os.write(secType);
+ }
+
+ return secType;
+ }
+
+ //
+ // Perform "no authentication".
+ //
+
+ void authenticateNone() throws Exception {
+ if (clientMinor >= 8)
+ readSecurityResult("No authentication");
+ }
+
+ //
+ // Perform standard VNC Authentication.
+ //
+
+ void authenticateVNC(String pw) throws Exception {
+ byte[] challenge = new byte[16];
+ readFully(challenge);
+
+ if (pw.length() > 8)
+ pw = pw.substring(0, 8); // Truncate to 8 chars
+
+ // Truncate password on the first zero byte.
+ int firstZero = pw.indexOf(0);
+ if (firstZero != -1)
+ pw = pw.substring(0, firstZero);
+
+ byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
+ System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
+
+ DesCipher des = new DesCipher(key);
+
+ des.encrypt(challenge, 0, challenge, 0);
+ des.encrypt(challenge, 8, challenge, 8);
+
+ os.write(challenge);
+
+ readSecurityResult("VNC authentication");
+ }
+
+ //
+ // Read security result.
+ // Throws an exception on authentication failure.
+ //
+
+ void readSecurityResult(String authType) throws Exception {
+ int securityResult = is.readInt();
+
+ switch (securityResult) {
+ case VncAuthOK:
+ System.out.println(authType + ": success");
+ break;
+ case VncAuthFailed:
+ if (clientMinor >= 8)
+ readConnFailedReason();
+ throw new Exception(authType + ": failed");
+ case VncAuthTooMany:
+ throw new Exception(authType + ": failed, too many tries");
+ default:
+ throw new Exception(authType + ": unknown result " + securityResult);
+ }
+ }
+
+ //
+ // Read the string describing the reason for a connection failure,
+ // and throw an exception.
+ //
+
+ void readConnFailedReason() throws Exception {
+ int reasonLen = is.readInt();
+ byte[] reason = new byte[reasonLen];
+ readFully(reason);
+ String reasonString = new String(reason);
+ Log.v(TAG, reasonString);
+ throw new Exception(reasonString);
+ }
+
+ void prepareDH() throws Exception
+ {
+ long gen = is.readLong();
+ long mod = is.readLong();
+ dh_resp = is.readLong();
+
+ dh = new DH(gen,mod);
+ long pub = dh.createInterKey();
+
+ os.write(DH.longToBytes(pub));
+ }
+
+ void authenticateDH(String us,String pw) throws Exception
+ {
+ long key = dh.createEncryptionKey(dh_resp);
+
+ DesCipher des = new DesCipher(DH.longToBytes(key));
+
+ byte user[] = new byte[256];
+ byte passwd[] = new byte[64];
+ int i;
+ System.arraycopy(us.getBytes(),0,user,0,us.length());
+ if(us.length() < 256)
+ {
+ for(i=us.length(); i<256; i++)
+ {
+ user[i]=0;
+ }
+ }
+ System.arraycopy(pw.getBytes(),0,passwd,0,pw.length());
+ if(pw.length() < 64)
+ {
+ for(i=pw.length(); i<64; i++)
+ {
+ passwd[i]=0;
+ }
+ }
+
+ des.encryptText(user,user,DH.longToBytes(key));
+ des.encryptText(passwd,passwd,DH.longToBytes(key));
+
+ os.write(user);
+ os.write(passwd);
+
+ readSecurityResult("VNC authentication");
+ }
+ //
+ // Initialize capability lists (TightVNC protocol extensions).
+ //
+
+ void initCapabilities() {
+ tunnelCaps = new CapsContainer();
+ authCaps = new CapsContainer();
+ serverMsgCaps = new CapsContainer();
+ clientMsgCaps = new CapsContainer();
+ encodingCaps = new CapsContainer();
+
+ // Supported authentication methods
+ authCaps.add(AuthNone, StandardVendor, SigAuthNone,
+ "No authentication");
+ authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
+ "Standard VNC password authentication");
+
+ // Supported encoding types
+ encodingCaps.add(EncodingCopyRect, StandardVendor,
+ SigEncodingCopyRect, "Standard CopyRect encoding");
+ encodingCaps.add(EncodingRRE, StandardVendor,
+ SigEncodingRRE, "Standard RRE encoding");
+ encodingCaps.add(EncodingCoRRE, StandardVendor,
+ SigEncodingCoRRE, "Standard CoRRE encoding");
+ encodingCaps.add(EncodingHextile, StandardVendor,
+ SigEncodingHextile, "Standard Hextile encoding");
+ encodingCaps.add(EncodingZRLE, StandardVendor,
+ SigEncodingZRLE, "Standard ZRLE encoding");
+ encodingCaps.add(EncodingZlib, TridiaVncVendor,
+ SigEncodingZlib, "Zlib encoding");
+ encodingCaps.add(EncodingTight, TightVncVendor,
+ SigEncodingTight, "Tight encoding");
+
+ // Supported pseudo-encoding types
+ encodingCaps.add(EncodingCompressLevel0, TightVncVendor,
+ SigEncodingCompressLevel0, "Compression level");
+ encodingCaps.add(EncodingQualityLevel0, TightVncVendor,
+ SigEncodingQualityLevel0, "JPEG quality level");
+ encodingCaps.add(EncodingXCursor, TightVncVendor,
+ SigEncodingXCursor, "X-style cursor shape update");
+ encodingCaps.add(EncodingRichCursor, TightVncVendor,
+ SigEncodingRichCursor, "Rich-color cursor shape update");
+ encodingCaps.add(EncodingPointerPos, TightVncVendor,
+ SigEncodingPointerPos, "Pointer position update");
+ encodingCaps.add(EncodingLastRect, TightVncVendor,
+ SigEncodingLastRect, "LastRect protocol extension");
+ encodingCaps.add(EncodingNewFBSize, TightVncVendor,
+ SigEncodingNewFBSize, "Framebuffer size change");
+ }
+
+ //
+ // Setup tunneling (TightVNC protocol extensions)
+ //
+
+ void setupTunneling() throws IOException {
+ int nTunnelTypes = is.readInt();
+ if (nTunnelTypes != 0) {
+ readCapabilityList(tunnelCaps, nTunnelTypes);
+
+ // We don't support tunneling yet.
+ writeInt(NoTunneling);
+ }
+ }
+
+ //
+ // Negotiate authentication scheme (TightVNC protocol extensions)
+ //
+
+ int negotiateAuthenticationTight() throws Exception {
+ int nAuthTypes = is.readInt();
+ if (nAuthTypes == 0)
+ return AuthNone;
+
+ readCapabilityList(authCaps, nAuthTypes);
+ for (int i = 0; i < authCaps.numEnabled(); i++) {
+ int authType = authCaps.getByOrder(i);
+ if (authType == AuthNone || authType == AuthVNC) {
+ writeInt(authType);
+ return authType;
+ }
+ }
+ throw new Exception("No suitable authentication scheme found");
+ }
+
+ //
+ // Read a capability list (TightVNC protocol extensions)
+ //
+
+ void readCapabilityList(CapsContainer caps, int count) throws IOException {
+ int code;
+ byte[] vendor = new byte[4];
+ byte[] name = new byte[8];
+ for (int i = 0; i < count; i++) {
+ code = is.readInt();
+ readFully(vendor);
+ readFully(name);
+ caps.enable(new CapabilityInfo(code, vendor, name));
+ }
+ }
+
+ //
+ // Write a 32-bit integer into the output stream.
+ //
+
+ byte[] writeIntBuffer = new byte[4];
+ void writeInt(int value) throws IOException {
+ writeIntBuffer[0] = (byte) ((value >> 24) & 0xff);
+ writeIntBuffer[1] = (byte) ((value >> 16) & 0xff);
+ writeIntBuffer[2] = (byte) ((value >> 8) & 0xff);
+ writeIntBuffer[3] = (byte) (value & 0xff);
+ os.write(writeIntBuffer);
+ }
+
+ //
+ // Write the client initialisation message
+ //
+
+ void writeClientInit() throws IOException {
+ /*- if (viewer.options.shareDesktop) {
+ os.write(1);
+ } else {
+ os.write(0);
+ }
+ viewer.options.disableShareDesktop();
+ */
+ os.write(0);
+ }
+
+
+ //
+ // Read the server initialisation message
+ //
+
+ String desktopName;
+ int framebufferWidth, framebufferHeight;
+ int bitsPerPixel, depth;
+ boolean bigEndian, trueColour;
+ int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
+
+ void readServerInit() throws IOException {
+ framebufferWidth = is.readUnsignedShort();
+ framebufferHeight = is.readUnsignedShort();
+ bitsPerPixel = is.readUnsignedByte();
+ depth = is.readUnsignedByte();
+ bigEndian = (is.readUnsignedByte() != 0);
+ trueColour = (is.readUnsignedByte() != 0);
+ redMax = is.readUnsignedShort();
+ greenMax = is.readUnsignedShort();
+ blueMax = is.readUnsignedShort();
+ redShift = is.readUnsignedByte();
+ greenShift = is.readUnsignedByte();
+ blueShift = is.readUnsignedByte();
+ byte[] pad = new byte[3];
+ readFully(pad);
+ int nameLength = is.readInt();
+ byte[] name = new byte[nameLength];
+ readFully(name);
+ desktopName = new String(name);
+
+ // Read interaction capabilities (TightVNC protocol extensions)
+ if (protocolTightVNC) {
+ int nServerMessageTypes = is.readUnsignedShort();
+ int nClientMessageTypes = is.readUnsignedShort();
+ int nEncodingTypes = is.readUnsignedShort();
+ is.readUnsignedShort();
+ readCapabilityList(serverMsgCaps, nServerMessageTypes);
+ readCapabilityList(clientMsgCaps, nClientMessageTypes);
+ readCapabilityList(encodingCaps, nEncodingTypes);
+ }
+
+ inNormalProtocol = true;
+ }
+
+
+ //
+ // Create session file and write initial protocol messages into it.
+ //
+ /*-
+ void startSession(String fname) throws IOException {
+ rec = new SessionRecorder(fname);
+ rec.writeHeader();
+ rec.write(versionMsg_3_3.getBytes());
+ rec.writeIntBE(SecTypeNone);
+ rec.writeShortBE(framebufferWidth);
+ rec.writeShortBE(framebufferHeight);
+ byte[] fbsServerInitMsg = {
+ 32, 24, 0, 1, 0,
+ (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF,
+ 16, 8, 0, 0, 0, 0
+ };
+ rec.write(fbsServerInitMsg);
+ rec.writeIntBE(desktopName.length());
+ rec.write(desktopName.getBytes());
+ numUpdatesInSession = 0;
+
+ // FIXME: If there were e.g. ZRLE updates only, that should not
+ // affect recording of Zlib and Tight updates. So, actually
+ // we should maintain separate flags for Zlib, ZRLE and
+ // Tight, instead of one ``wereZlibUpdates'' variable.
+ //
+ if (wereZlibUpdates)
+ recordFromBeginning = false;
+
+ zlibWarningShown = false;
+ tightWarningShown = false;
+ }
+
+ //
+ // Close session file.
+ //
+
+ void closeSession() throws IOException {
+ if (rec != null) {
+ rec.close();
+ rec = null;
+ }
+ }
+ */
+
+ //
+ // Set new framebuffer size
+ //
+
+ void setFramebufferSize(int width, int height) {
+ framebufferWidth = width;
+ framebufferHeight = height;
+ }
+
+
+ //
+ // Read the server message type
+ //
+
+ int readServerMessageType() throws IOException {
+ int msgType = is.readUnsignedByte();
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ if (msgType == Bell) { // Save Bell messages in session files.
+ rec.writeByte(msgType);
+ if (numUpdatesInSession > 0)
+ rec.flush();
+ }
+ }
+ */
+
+ return msgType;
+ }
+
+
+ //
+ // Read a FramebufferUpdate message
+ //
+
+ int updateNRects;
+
+ void readFramebufferUpdate() throws IOException {
+ is.readByte();
+ updateNRects = is.readUnsignedShort();
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ rec.writeByte(FramebufferUpdate);
+ rec.writeByte(0);
+ rec.writeShortBE(updateNRects);
+ }
+ */
+
+ numUpdatesInSession++;
+ }
+
+ // Read a FramebufferUpdate rectangle header
+
+ int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
+
+ void readFramebufferUpdateRectHdr() throws Exception {
+ updateRectX = is.readUnsignedShort();
+ updateRectY = is.readUnsignedShort();
+ updateRectW = is.readUnsignedShort();
+ updateRectH = is.readUnsignedShort();
+ updateRectEncoding = is.readInt();
+
+ if (updateRectEncoding == EncodingZlib ||
+ updateRectEncoding == EncodingZRLE ||
+ updateRectEncoding == EncodingTight)
+ wereZlibUpdates = true;
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ if (numUpdatesInSession > 1)
+ rec.flush(); // Flush the output on each rectangle.
+ rec.writeShortBE(updateRectX);
+ rec.writeShortBE(updateRectY);
+ rec.writeShortBE(updateRectW);
+ rec.writeShortBE(updateRectH);
+ if (updateRectEncoding == EncodingZlib && !recordFromBeginning) {
+ // Here we cannot write Zlib-encoded rectangles because the
+ // decoder won't be able to reproduce zlib stream state.
+ if (!zlibWarningShown) {
+ System.out.println("Warning: Raw encoding will be used " +
+ "instead of Zlib in recorded session.");
+ zlibWarningShown = true;
+ }
+ rec.writeIntBE(EncodingRaw);
+ } else {
+ rec.writeIntBE(updateRectEncoding);
+ if (updateRectEncoding == EncodingTight && !recordFromBeginning &&
+ !tightWarningShown) {
+ System.out.println("Warning: Re-compressing Tight-encoded " +
+ "updates for session recording.");
+ tightWarningShown = true;
+ }
+ }
+ }
+ */
+
+ if (updateRectEncoding != RfbProto.EncodingPointerPos && ( updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding ))
+ return;
+
+ if (updateRectX + updateRectW > framebufferWidth ||
+ updateRectY + updateRectH > framebufferHeight) {
+ throw new Exception("Framebuffer update rectangle too large: " +
+ updateRectW + "x" + updateRectH + " at (" +
+ updateRectX + "," + updateRectY + ")");
+ }
+ }
+
+ // Read CopyRect source X and Y.
+
+ int copyRectSrcX, copyRectSrcY;
+
+ void readCopyRect() throws IOException {
+ copyRectSrcX = is.readUnsignedShort();
+ copyRectSrcY = is.readUnsignedShort();
+
+ // If the session is being recorded:
+ /*-
+ if (rec != null) {
+ rec.writeShortBE(copyRectSrcX);
+ rec.writeShortBE(copyRectSrcY);
+ }
+ */
+ }
+
+
+ //
+ // Read a ServerCutText message
+ //
+
+ String readServerCutText() throws IOException {
+ byte[] pad = new byte[3];
+ readFully(pad);
+ int len = is.readInt();
+ byte[] text = new byte[len];
+ readFully(text);
+ return new String(text);
+ }
+
+
+ //
+ // Read an integer in compact representation (1..3 bytes).
+ // Such format is used as a part of the Tight encoding.
+ // Also, this method records data if session recording is active and
+ // the viewer's recordFromBeginning variable is set to true.
+ //
+
+ int readCompactLen() throws IOException {
+ int[] portion = new int[3];
+ portion[0] = is.readUnsignedByte();
+ int byteCount = 1;
+ int len = portion[0] & 0x7F;
+ if ((portion[0] & 0x80) != 0) {
+ portion[1] = is.readUnsignedByte();
+ byteCount++;
+ len |= (portion[1] & 0x7F) << 7;
+ if ((portion[1] & 0x80) != 0) {
+ portion[2] = is.readUnsignedByte();
+ byteCount++;
+ len |= (portion[2] & 0xFF) << 14;
+ }
+ }
+ /*-
+ if (rec != null && recordFromBeginning)
+ for (int i = 0; i < byteCount; i++)
+ rec.writeByte(portion[i]);
+ */
+ return len;
+ }
+
+
+ //
+ // Write a FramebufferUpdateRequest message
+ //
+
+ byte[] framebufferUpdateRequest = new byte[10];
+ synchronized void writeFramebufferUpdateRequest(int x, int y, int w, int h,
+ boolean incremental)
+ throws IOException
+ {
+ framebufferUpdateRequest[0] = (byte) FramebufferUpdateRequest;
+ framebufferUpdateRequest[1] = (byte) (incremental ? 1 : 0);
+ framebufferUpdateRequest[2] = (byte) ((x >> 8) & 0xff);
+ framebufferUpdateRequest[3] = (byte) (x & 0xff);
+ framebufferUpdateRequest[4] = (byte) ((y >> 8) & 0xff);
+ framebufferUpdateRequest[5] = (byte) (y & 0xff);
+ framebufferUpdateRequest[6] = (byte) ((w >> 8) & 0xff);
+ framebufferUpdateRequest[7] = (byte) (w & 0xff);
+ framebufferUpdateRequest[8] = (byte) ((h >> 8) & 0xff);
+ framebufferUpdateRequest[9] = (byte) (h & 0xff);
+
+ os.write(framebufferUpdateRequest);
+ }
+
+
+ //
+ // Write a SetPixelFormat message
+ //
+
+ synchronized void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian,
+ boolean trueColour,
+ int redMax, int greenMax, int blueMax,
+ int redShift, int greenShift, int blueShift, boolean fGreyScale) // sf@2005)
+ throws IOException
+ {
+ byte[] b = new byte[20];
+
+ b[0] = (byte) SetPixelFormat;
+ b[4] = (byte) bitsPerPixel;
+ b[5] = (byte) depth;
+ b[6] = (byte) (bigEndian ? 1 : 0);
+ b[7] = (byte) (trueColour ? 1 : 0);
+ b[8] = (byte) ((redMax >> 8) & 0xff);
+ b[9] = (byte) (redMax & 0xff);
+ b[10] = (byte) ((greenMax >> 8) & 0xff);
+ b[11] = (byte) (greenMax & 0xff);
+ b[12] = (byte) ((blueMax >> 8) & 0xff);
+ b[13] = (byte) (blueMax & 0xff);
+ b[14] = (byte) redShift;
+ b[15] = (byte) greenShift;
+ b[16] = (byte) blueShift;
+ b[17] = (byte) (fGreyScale ? 1 : 0); // sf@2005
+
+ os.write(b);
+ }
+
+
+ //
+ // Write a FixColourMapEntries message. The values in the red, green and
+ // blue arrays are from 0 to 65535.
+ //
+
+ synchronized void writeFixColourMapEntries(int firstColour, int nColours,
+ int[] red, int[] green, int[] blue)
+ throws IOException
+ {
+ byte[] b = new byte[6 + nColours * 6];
+
+ b[0] = (byte) FixColourMapEntries;
+ b[2] = (byte) ((firstColour >> 8) & 0xff);
+ b[3] = (byte) (firstColour & 0xff);
+ b[4] = (byte) ((nColours >> 8) & 0xff);
+ b[5] = (byte) (nColours & 0xff);
+
+ for (int i = 0; i < nColours; i++) {
+ b[6 + i * 6] = (byte) ((red[i] >> 8) & 0xff);
+ b[6 + i * 6 + 1] = (byte) (red[i] & 0xff);
+ b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff);
+ b[6 + i * 6 + 3] = (byte) (green[i] & 0xff);
+ b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff);
+ b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff);
+ }
+
+ os.write(b);
+ }
+
+
+ //
+ // Write a SetEncodings message
+ //
+
+ synchronized void writeSetEncodings(int[] encs, int len) throws IOException {
+ byte[] b = new byte[4 + 4 * len];
+
+ b[0] = (byte) SetEncodings;
+ b[2] = (byte) ((len >> 8) & 0xff);
+ b[3] = (byte) (len & 0xff);
+
+ for (int i = 0; i < len; i++) {
+ b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff);
+ b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff);
+ b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff);
+ b[7 + 4 * i] = (byte) (encs[i] & 0xff);
+ }
+
+ os.write(b);
+ }
+
+
+ //
+ // Write a ClientCutText message
+ //
+
+ synchronized void writeClientCutText(String text) throws IOException {
+ byte[] b = new byte[8 + text.length()];
+
+ b[0] = (byte) ClientCutText;
+ b[4] = (byte) ((text.length() >> 24) & 0xff);
+ b[5] = (byte) ((text.length() >> 16) & 0xff);
+ b[6] = (byte) ((text.length() >> 8) & 0xff);
+ b[7] = (byte) (text.length() & 0xff);
+
+ System.arraycopy(text.getBytes(), 0, b, 8, text.length());
+
+ os.write(b);
+ }
+
+
+ //
+ // A buffer for putting pointer and keyboard events before being sent. This
+ // is to ensure that multiple RFB events generated from a single Java Event
+ // will all be sent in a single network packet. The maximum possible
+ // length is 4 modifier down events, a single key event followed by 4
+ // modifier up events i.e. 9 key events or 72 bytes.
+ //
+
+ byte[] eventBuf = new byte[72];
+ int eventBufLen;
+
+
+ /**
+ * Write a pointer event message. We may need to send modifier key events
+ * around it to set the correct modifier state.
+ * @param x
+ * @param y
+ * @param modifiers
+ * @param pointerMask
+ * @throws IOException
+ */
+ synchronized void writePointerEvent( int x, int y, int modifiers, int pointerMask) throws IOException
+ {
+ eventBufLen = 0;
+ writeModifierKeyEvents(modifiers);
+
+ eventBuf[eventBufLen++] = (byte) PointerEvent;
+ eventBuf[eventBufLen++] = (byte) pointerMask;
+ eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
+ eventBuf[eventBufLen++] = (byte) (x & 0xff);
+ eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
+ eventBuf[eventBufLen++] = (byte) (y & 0xff);
+
+ //
+ // Always release all modifiers after an "up" event
+ //
+
+ if (pointerMask == 0) {
+ writeModifierKeyEvents(0);
+ }
+
+ os.write(eventBuf, 0, eventBufLen);
+ }
+
+ void writeCtrlAltDel() throws IOException {
+ final int DELETE = 0xffff;
+ final int CTRLALT = VncCanvas.CTRL_MASK | VncCanvas.ALT_MASK;
+ try {
+ // Press
+ eventBufLen = 0;
+ writeModifierKeyEvents(CTRLALT);
+ writeKeyEvent(DELETE, true);
+ os.write(eventBuf, 0, eventBufLen);
+
+ // Release
+ eventBufLen = 0;
+ writeModifierKeyEvents(CTRLALT);
+ writeKeyEvent(DELETE, false);
+
+ // Reset VNC server modifiers state
+ writeModifierKeyEvents(0);
+ os.write(eventBuf, 0, eventBufLen);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ //
+ // Write a key event message. We may need to send modifier key events
+ // around it to set the correct modifier state. Also we need to translate
+ // from the Java key values to the X keysym values used by the RFB protocol.
+ //
+ synchronized void writeKeyEvent(int keySym, int metaState, boolean down) throws IOException {
+ eventBufLen = 0;
+ if (down)
+ writeModifierKeyEvents(metaState);
+ if (keySym != 0)
+ writeKeyEvent(keySym, down);
+
+ // Always release all modifiers after an "up" event
+ if (!down)
+ writeModifierKeyEvents(0);
+
+ os.write(eventBuf, 0, eventBufLen);
+ }
+
+
+
+
+ //
+ // Add a raw key event with the given X keysym to eventBuf.
+ //
+
+ private void writeKeyEvent(int keysym, boolean down) {
+ eventBuf[eventBufLen++] = (byte) KeyboardEvent;
+ eventBuf[eventBufLen++] = (byte) (down ? 1 : 0);
+ eventBuf[eventBufLen++] = (byte) 0;
+ eventBuf[eventBufLen++] = (byte) 0;
+ eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff);
+ eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff);
+ eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff);
+ eventBuf[eventBufLen++] = (byte) (keysym & 0xff);
+ }
+
+
+ //
+ // Write key events to set the correct modifier state.
+ //
+
+ int oldModifiers = 0;
+
+ void writeModifierKeyEvents(int newModifiers) {
+ if ((newModifiers & VncCanvas.CTRL_MASK) != (oldModifiers & VncCanvas.CTRL_MASK))
+ writeKeyEvent(0xffe3, (newModifiers & VncCanvas.CTRL_MASK) != 0);
+
+ if ((newModifiers & VncCanvas.SHIFT_MASK) != (oldModifiers & VncCanvas.SHIFT_MASK))
+ writeKeyEvent(0xffe1, (newModifiers & VncCanvas.SHIFT_MASK) != 0);
+
+ if ((newModifiers & VncCanvas.META_MASK) != (oldModifiers & VncCanvas.META_MASK))
+ writeKeyEvent(0xffe7, (newModifiers & VncCanvas.META_MASK) != 0);
+
+ if ((newModifiers & VncCanvas.ALT_MASK) != (oldModifiers & VncCanvas.ALT_MASK))
+ writeKeyEvent(0xffe9, (newModifiers & VncCanvas.ALT_MASK) != 0);
+
+ oldModifiers = newModifiers;
+ }
+ //
+ // Compress and write the data into the recorded session file. This
+ // method assumes the recording is on (rec != null).
+ //
+
+
+ public void startTiming() {
+ timing = true;
+
+ // Carry over up to 1s worth of previous rate for smoothing.
+
+ if (timeWaitedIn100us > 10000) {
+ timedKbits = timedKbits * 10000 / timeWaitedIn100us;
+ timeWaitedIn100us = 10000;
+ }
+ }
+
+ public void stopTiming() {
+ timing = false;
+ if (timeWaitedIn100us < timedKbits/2)
+ timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
+ }
+
+ public long kbitsPerSecond() {
+ return timedKbits * 10000 / timeWaitedIn100us;
+ }
+
+ public long timeWaited() {
+ return timeWaitedIn100us;
+ }
+
+ public void readFully(byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ public void readFully(byte b[], int off, int len) throws IOException {
+ long before = 0;
+ timing = false; // for test
+
+ if (timing)
+ before = System.currentTimeMillis();
+
+ is.readFully(b, off, len);
+
+ if (timing) {
+ long after = System.currentTimeMillis();
+ long newTimeWaited = (after - before) * 10;
+ int newKbits = len * 8 / 1000;
+
+ // limit rate to between 10kbit/s and 40Mbit/s
+
+ if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
+ if (newTimeWaited < newKbits/4) newTimeWaited = newKbits/4;
+
+ timeWaitedIn100us += newTimeWaited;
+ timedKbits += newKbits;
+ }
+ }
+
+ synchronized void writeOpenChat() throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ writeInt(CHAT_OPEN); // int message length
+ }
+
+ synchronized void writeCloseChat() throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ writeInt(CHAT_CLOSE); // int message length
+ }
+
+ synchronized void writeFinishedChat() throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ writeInt(CHAT_FINISHED); // int message length
+ }
+
+ String readTextChatMsg() throws Exception {
+ byte[] pad = new byte[3];
+ readFully(pad);
+ int len = is.readInt();
+ if (len == CHAT_OPEN) {
+ // Remote user requests chat
+ ///viewer.openChat();
+ // Respond to chat request
+ writeOpenChat();
+ return null;
+ } else if (len == CHAT_CLOSE) {
+ // Remote user ends chat
+ ///viewer.closeChat();
+ return null;
+ } else if (len == CHAT_FINISHED) {
+ // Remote user says chat finished.
+ // Not sure why I should care about this state.
+ return null;
+ } else {
+ // Remote user sends message!!
+ if (len > 0) {
+ byte[] msg = new byte[len];
+ readFully(msg);
+ return new String(msg);
+ }
+ }
+ return null;
+ }
+
+ public synchronized void writeChatMessage(String msg) throws Exception {
+ os.write(TextChat); // byte type
+ os.write(0); // byte pad 1
+ os.write(0); // byte pad 2
+ os.write(0); // byte pad 2
+ byte [] bytes = msg.getBytes("8859_1");
+ byte [] outgoing = bytes;
+ if (bytes.length > 4096) {
+ outgoing = new byte[4096];
+ System.arraycopy(bytes, 0, outgoing, 0, 4096);
+ }
+ writeInt(outgoing.length); // int message length
+ os.write(outgoing); // message
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/SentTextBean.java b/app/src/main/java/android/androidVNC/SentTextBean.java
new file mode 100644
index 000000000..96769b189
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/SentTextBean.java
@@ -0,0 +1,84 @@
+// This class was generated from android.androidVNC.ISentText by a tool
+// Do not edit this file directly! PLX THX
+package android.androidVNC;
+
+public class SentTextBean extends com.antlersoft.android.dbimpl.IdImplementationBase implements ISentText {
+
+ public static final String GEN_TABLE_NAME = "SENT_TEXT";
+ public static final int GEN_COUNT = 2;
+
+ // Field constants
+ public static final String GEN_FIELD__ID = "_id";
+ public static final int GEN_ID__ID = 0;
+ public static final String GEN_FIELD_SENTTEXT = "SENTTEXT";
+ public static final int GEN_ID_SENTTEXT = 1;
+
+ // SQL Command for creating the table
+ public static String GEN_CREATE = "CREATE TABLE SENT_TEXT (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "SENTTEXT TEXT" +
+ ")";
+
+ // Members corresponding to defined fields
+ private long gen__Id;
+ private java.lang.String gen_sentText;
+
+
+ public static final com.antlersoft.android.dbimpl.NewInstance GEN_NEW = new com.antlersoft.android.dbimpl.NewInstance() {
+ public SentTextBean get() {
+ return new SentTextBean();
+ }
+ }
+ ;
+
+ public String Gen_tableName() { return GEN_TABLE_NAME; }
+
+ // Field accessors
+ public long get_Id() { return gen__Id; }
+ public void set_Id(long arg__Id) { gen__Id = arg__Id; }
+ public java.lang.String getSentText() { return gen_sentText; }
+ public void setSentText(java.lang.String arg_sentText) { gen_sentText = arg_sentText; }
+
+ public android.content.ContentValues Gen_getValues() {
+ android.content.ContentValues values=new android.content.ContentValues();
+ values.put(GEN_FIELD__ID,Long.toString(this.gen__Id));
+ values.put(GEN_FIELD_SENTTEXT,this.gen_sentText);
+ return values;
+ }
+
+ /**
+ * Return an array that gives the column index in the cursor for each field defined
+ * @param cursor Database cursor over some columns, possibly including this table
+ * @return array of column indices; -1 if the column with that id is not in cursor
+ */
+ public int[] Gen_columnIndices(android.database.Cursor cursor) {
+ int[] result=new int[GEN_COUNT];
+ result[0] = cursor.getColumnIndex(GEN_FIELD__ID);
+ // Make compatible with database generated by older version of plugin with uppercase column name
+ if (result[0] == -1) {
+ result[0] = cursor.getColumnIndex("_ID");
+ }
+ result[1] = cursor.getColumnIndex(GEN_FIELD_SENTTEXT);
+ return result;
+ }
+
+ /**
+ * Populate one instance from a cursor
+ */
+ public void Gen_populate(android.database.Cursor cursor,int[] columnIndices) {
+ if ( columnIndices[GEN_ID__ID] >= 0 && ! cursor.isNull(columnIndices[GEN_ID__ID])) {
+ gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
+ }
+ if ( columnIndices[GEN_ID_SENTTEXT] >= 0 && ! cursor.isNull(columnIndices[GEN_ID_SENTTEXT])) {
+ gen_sentText = cursor.getString(columnIndices[GEN_ID_SENTTEXT]);
+ }
+ }
+
+ /**
+ * Populate one instance from a ContentValues
+ */
+ public void Gen_populate(android.content.ContentValues values) {
+ gen__Id = values.getAsLong(GEN_FIELD__ID);
+ gen_sentText = values.getAsString(GEN_FIELD_SENTTEXT);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/Utils.java b/app/src/main/java/android/androidVNC/Utils.java
new file mode 100644
index 000000000..b4034489b
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/Utils.java
@@ -0,0 +1,75 @@
+package android.androidVNC;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.Html;
+
+public class Utils {
+
+ public static void showYesNoPrompt(Context _context, String title, String message, OnClickListener onYesListener, OnClickListener onNoListener) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(_context);
+ builder.setTitle(title);
+ builder.setIcon(android.R.drawable.ic_dialog_info); // lame icon
+ builder.setMessage(message);
+ builder.setCancelable(false);
+ builder.setPositiveButton("Yes", onYesListener);
+ builder.setNegativeButton("No", onNoListener);
+ builder.show();
+ }
+
+ private static final Intent docIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://code.google.com/p/android-vnc-viewer/wiki/Documentation"));
+
+ public static ActivityManager getActivityManager(Context context)
+ {
+ ActivityManager result = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+ if (result == null)
+ throw new UnsupportedOperationException("Could not retrieve ActivityManager");
+ return result;
+ }
+
+ public static MemoryInfo getMemoryInfo(Context _context) {
+ MemoryInfo info = new MemoryInfo();
+ getActivityManager(_context).getMemoryInfo(info);
+ return info;
+ }
+
+ public static void showDocumentation(Context c) {
+ c.startActivity(docIntent);
+ }
+
+ private static int nextNoticeID = 0;
+ public static int nextNoticeID() {
+ nextNoticeID++;
+ return nextNoticeID;
+ }
+
+ public static void showErrorMessage(Context _context, String message) {
+ showMessage(_context, "Error!", message, android.R.drawable.ic_dialog_alert, null);
+ }
+
+ public static void showFatalErrorMessage(final Context _context, String message) {
+ showMessage(_context, "Fatal Error!", message, android.R.drawable.ic_dialog_alert, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // ((Activity) _context).finish();
+ }
+ });
+ }
+
+ public static void showMessage(Context _context, String title, String message, int icon, DialogInterface.OnClickListener ackHandler) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(_context);
+ builder.setTitle(title);
+ builder.setMessage(Html.fromHtml(message));
+ builder.setCancelable(false);
+ builder.setPositiveButton("Acknowledged", ackHandler);
+ builder.setIcon(icon);
+ builder.show();
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/VncCanvas.java b/app/src/main/java/android/androidVNC/VncCanvas.java
new file mode 100644
index 000000000..8d3ee70e9
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncCanvas.java
@@ -0,0 +1,1635 @@
+//
+// Copyright (C) 2010 Michael A. MacDonald
+// Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
+// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
+// Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved.
+// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
+// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+//
+// This is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this software; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+// USA.
+//
+
+//
+// VncCanvas is a subclass of android.view.SurfaceView which draws a VNC
+// desktop on it.
+//
+
+package android.androidVNC;
+
+import java.io.IOException;
+import java.util.zip.Inflater;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Paint.Style;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.antlersoft.android.bc.BCFactory;
+
+
+public class VncCanvas extends ImageView {
+ private final static String TAG = "VncCanvas";
+ private final static boolean LOCAL_LOGV = true;
+
+ AbstractScaling scaling;
+
+ // Available to activity
+ int mouseX, mouseY;
+
+ // Connection parameters
+ ConnectionBean connection;
+
+ // Runtime control flags
+ private boolean maintainConnection = true;
+ private boolean showDesktopInfo = true;
+ private boolean repaintsEnabled = true;
+
+ /**
+ * Use camera button as meta key for right mouse button
+ */
+ boolean cameraButtonDown = false;
+
+ // Keep track when a seeming key press was the result of a menu shortcut
+ int lastKeyDown;
+ boolean afterMenu;
+
+ // Color Model settings
+ private COLORMODEL pendingColorModel = COLORMODEL.C24bit;
+ private COLORMODEL colorModel = null;
+ private int bytesPerPixel = 0;
+ private int[] colorPalette = null;
+
+ // VNC protocol connection
+ public RfbProto rfb;
+
+ // Internal bitmap data
+ AbstractBitmapData bitmapData;
+ public Handler handler = new Handler();
+
+ // VNC Encoding parameters
+ private boolean useCopyRect = false; // TODO CopyRect is not working
+ private int preferredEncoding = -1;
+
+ // Unimplemented VNC encoding parameters
+ private boolean requestCursorUpdates = false;
+ private boolean ignoreCursorUpdates = true;
+
+ // Unimplemented TIGHT encoding parameters
+ private int compressLevel = -1;
+ private int jpegQuality = -1;
+
+ // Used to determine if encoding update is necessary
+ private int[] encodingsSaved = new int[20];
+ private int nEncodingsSaved = 0;
+
+ // ZRLE encoder's data.
+ private byte[] zrleBuf;
+ private int[] zrleTilePixels;
+ private ZlibInStream zrleInStream;
+
+ // Zlib encoder's data.
+ private byte[] zlibBuf;
+ private Inflater zlibInflater;
+ private MouseScrollRunnable scrollRunnable;
+
+ private Paint handleRREPaint;
+
+ /**
+ * Position of the top left portion of the visible part of the screen, in
+ * full-frame coordinates
+ */
+ int absoluteXPosition = 0, absoluteYPosition = 0;
+
+ /**
+ * Constructor used by the inflation apparatus
+ * @param context
+ */
+ public VncCanvas(final Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ scrollRunnable = new MouseScrollRunnable();
+ handleRREPaint = new Paint();
+ handleRREPaint.setStyle(Style.FILL);
+ }
+
+ /**
+ * Create a view showing a VNC connection
+ * @param context Containing context (activity)
+ * @param bean Connection settings
+ * @param setModes Callback to run on UI thread after connection is set up
+ */
+ void initializeVncCanvas(ConnectionBean bean, final Runnable setModes) {
+ connection = bean;
+ this.pendingColorModel = COLORMODEL.valueOf(bean.getColorModel());
+
+ // Startup the RFB thread with a nifty progess dialog
+ final ProgressDialog pd = ProgressDialog.show(getContext(), "Connecting...", "Establishing handshake.\nPlease wait...", true, true, new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ closeConnection();
+ handler.post(new Runnable() {
+ public void run() {
+ Utils.showErrorMessage(getContext(), "VNC connection aborted!");
+ }
+ });
+ }
+ });
+ final Display display = pd.getWindow().getWindowManager().getDefaultDisplay();
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ connectAndAuthenticate(connection.getUserName(),connection.getPassword());
+ doProtocolInitialisation(display.getWidth(), display.getHeight());
+ handler.post(new Runnable() {
+ public void run() {
+ pd.setMessage("Downloading first frame.\nPlease wait...");
+ }
+ });
+ processNormalProtocol(getContext(), pd, setModes);
+ } catch (Throwable e) {
+ if (maintainConnection) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ // Ensure we dismiss the progress dialog
+ // before we fatal error finish
+ if (pd.isShowing())
+ pd.dismiss();
+ if (e instanceof OutOfMemoryError) {
+ // TODO Not sure if this will happen but...
+ // figure out how to gracefully notify the user
+ // Instantiating an alert dialog here doesn't work
+ // because we are out of memory. :(
+ } else {
+ String error = "VNC connection failed!";
+ if (e.getMessage() != null && (e.getMessage().indexOf("authentication") > -1)) {
+ error = "VNC authentication failed!";
+ }
+ final String error_ = error + " " + e.getLocalizedMessage();
+ handler.post(new Runnable() {
+ public void run() {
+ Utils.showFatalErrorMessage(getContext(), error_);
+ }
+ });
+ }
+ }
+ }
+ }
+ };
+ t.start();
+ }
+
+ void connectAndAuthenticate(String us,String pw) throws Exception {
+ Log.i(TAG, "Connecting to " + connection.getAddress() + ", port " + connection.getPort() + "...");
+
+ rfb = new RfbProto(connection.getAddress(), connection.getPort());
+ if (LOCAL_LOGV) Log.v(TAG, "Connected to server");
+
+ //
+ if (connection.getUseRepeater() && connection.getRepeaterId() != null && connection.getRepeaterId().length()>0) {
+ Log.i(TAG, "Negotiating repeater/proxy connection");
+ byte[] protocolMsg = new byte[12];
+ rfb.is.read(protocolMsg);
+ byte[] buffer = new byte[250];
+ System.arraycopy(connection.getRepeaterId().getBytes(), 0, buffer, 0, connection.getRepeaterId().length());
+ rfb.os.write(buffer);
+ }
+ //
+
+ rfb.readVersionMsg();
+ Log.i(TAG, "RFB server supports protocol version " + rfb.serverMajor + "" + rfb.serverMinor);
+
+ rfb.writeVersionMsg();
+ Log.i(TAG, "Using RFB protocol version " + rfb.clientMajor + "" + rfb.clientMinor);
+
+ int bitPref=0;
+ if(connection.getUserName().length()>0)
+ bitPref|=1;
+ Log.d("debug","bitPref="+bitPref);
+ int secType = rfb.negotiateSecurity(bitPref);
+ int authType;
+ if (secType == RfbProto.SecTypeTight) {
+ rfb.initCapabilities();
+ rfb.setupTunneling();
+ authType = rfb.negotiateAuthenticationTight();
+ } else if (secType == RfbProto.SecTypeUltra34) {
+ rfb.prepareDH();
+ authType = RfbProto.AuthUltra;
+ } else {
+ authType = secType;
+ }
+
+ switch (authType) {
+ case RfbProto.AuthNone:
+ Log.i(TAG, "No authentication needed");
+ rfb.authenticateNone();
+ break;
+ case RfbProto.AuthVNC:
+ Log.i(TAG, "VNC authentication needed");
+ rfb.authenticateVNC(pw);
+ break;
+ case RfbProto.AuthUltra:
+ rfb.authenticateDH(us,pw);
+ break;
+ default:
+ throw new Exception("Unknown authentication scheme " + authType);
+ }
+ }
+
+ void doProtocolInitialisation(int dx, int dy) throws IOException {
+ rfb.writeClientInit();
+ rfb.readServerInit();
+
+ Log.i(TAG, "Desktop name is " + rfb.desktopName);
+ Log.i(TAG, "Desktop size is " + rfb.framebufferWidth + " x " + rfb.framebufferHeight);
+
+ boolean useFull = false;
+ int capacity = BCFactory.getInstance().getBCActivityManager().getMemoryClass(Utils.getActivityManager(getContext()));
+ if (connection.getForceFull() == BitmapImplHint.AUTO)
+ {
+ if (rfb.framebufferWidth * rfb.framebufferHeight * FullBufferBitmapData.CAPACITY_MULTIPLIER <= capacity * 1024 * 1024)
+ useFull = true;
+ }
+ else
+ useFull = (connection.getForceFull() == BitmapImplHint.FULL);
+ if (! useFull)
+ bitmapData=new LargeBitmapData(rfb,this,dx,dy,capacity);
+ else
+ bitmapData=new FullBufferBitmapData(rfb,this, capacity);
+ mouseX=rfb.framebufferWidth/2;
+ mouseY=rfb.framebufferHeight/2;
+
+ setPixelFormat();
+ }
+
+ private void setPixelFormat() throws IOException {
+ pendingColorModel.setPixelFormat(rfb);
+ bytesPerPixel = pendingColorModel.bpp();
+ colorPalette = pendingColorModel.palette();
+ colorModel = pendingColorModel;
+ pendingColorModel = null;
+ }
+
+ public void setColorModel(COLORMODEL cm) {
+ // Only update if color model changes
+ if (colorModel == null || !colorModel.equals(cm))
+ pendingColorModel = cm;
+ }
+
+ public boolean isColorModel(COLORMODEL cm) {
+ return (colorModel != null) && colorModel.equals(cm);
+ }
+
+ private void mouseFollowPan()
+ {
+ if (connection.getFollowPan() && scaling.isAbleToPan())
+ {
+ int scrollx = absoluteXPosition;
+ int scrolly = absoluteYPosition;
+ int width = getVisibleWidth();
+ int height = getVisibleHeight();
+ //Log.i(TAG,"scrollx " + scrollx + " scrolly " + scrolly + " mouseX " + mouseX +" Y " + mouseY + " w " + width + " h " + height);
+ if (mouseX < scrollx || mouseX >= scrollx + width || mouseY < scrolly || mouseY >= scrolly + height)
+ {
+ //Log.i(TAG,"warp to " + scrollx+width/2 + "," + scrolly + height/2);
+ warpMouse(scrollx + width/2, scrolly + height / 2);
+ }
+ }
+ }
+
+ public void processNormalProtocol(final Context context, ProgressDialog pd, final Runnable setModes) throws Exception {
+ try {
+ bitmapData.writeFullUpdateRequest(false);
+
+ handler.post(setModes);
+ //
+ // main dispatch loop
+ //
+ while (maintainConnection) {
+ bitmapData.syncScroll();
+ // Read message type from the server.
+ int msgType = rfb.readServerMessageType();
+ bitmapData.doneWaiting();
+ // Process the message depending on its type.
+ switch (msgType) {
+ case RfbProto.FramebufferUpdate:
+ rfb.readFramebufferUpdate();
+
+ for (int i = 0; i < rfb.updateNRects; i++) {
+ rfb.readFramebufferUpdateRectHdr();
+ int rx = rfb.updateRectX, ry = rfb.updateRectY;
+ int rw = rfb.updateRectW, rh = rfb.updateRectH;
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingLastRect) {
+ Log.v(TAG, "rfb.EncodingLastRect");
+ break;
+ }
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingNewFBSize) {
+ rfb.setFramebufferSize(rw, rh);
+ // - updateFramebufferSize();
+ Log.v(TAG, "rfb.EncodingNewFBSize");
+ break;
+ }
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingXCursor || rfb.updateRectEncoding == RfbProto.EncodingRichCursor) {
+ // - handleCursorShapeUpdate(rfb.updateRectEncoding,
+ // rx,
+ // ry, rw, rh);
+ Log.v(TAG, "rfb.EncodingCursor");
+ continue;
+
+ }
+
+ if (rfb.updateRectEncoding == RfbProto.EncodingPointerPos) {
+ // This never actually happens
+ mouseX=rx;
+ mouseY=ry;
+ Log.v(TAG, "rfb.EncodingPointerPos");
+ continue;
+ }
+
+ rfb.startTiming();
+
+ switch (rfb.updateRectEncoding) {
+ case RfbProto.EncodingRaw:
+ handleRawRect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingCopyRect:
+ handleCopyRect(rx, ry, rw, rh);
+ Log.v(TAG, "CopyRect is Buggy!");
+ break;
+ case RfbProto.EncodingRRE:
+ handleRRERect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingCoRRE:
+ handleCoRRERect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingHextile:
+ handleHextileRect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingZRLE:
+ handleZRLERect(rx, ry, rw, rh);
+ break;
+ case RfbProto.EncodingZlib:
+ handleZlibRect(rx, ry, rw, rh);
+ break;
+ default:
+ Log.e(TAG, "Unknown RFB rectangle encoding " + rfb.updateRectEncoding + " (0x" + Integer.toHexString(rfb.updateRectEncoding) + ")");
+ }
+
+ rfb.stopTiming();
+
+ // Hide progress dialog
+ if (pd.isShowing())
+ pd.dismiss();
+ }
+
+ boolean fullUpdateNeeded = false;
+
+ if (pendingColorModel != null) {
+ setPixelFormat();
+ fullUpdateNeeded = true;
+ }
+
+ setEncodings(true);
+ bitmapData.writeFullUpdateRequest(!fullUpdateNeeded);
+
+ break;
+
+ case RfbProto.SetColourMapEntries:
+ throw new Exception("Can't handle SetColourMapEntries message");
+
+ case RfbProto.Bell:
+ handler.post( new Runnable() {
+ public void run() { Toast.makeText( context, "VNC Beep", Toast.LENGTH_SHORT); }
+ });
+ break;
+
+ case RfbProto.ServerCutText:
+ String s = rfb.readServerCutText();
+ if (s != null && s.length() > 0) {
+ // TODO implement cut & paste
+ }
+ break;
+
+ case RfbProto.TextChat:
+ // UltraVNC extension
+ String msg = rfb.readTextChatMsg();
+ if (msg != null && msg.length() > 0) {
+ // TODO implement chat interface
+ }
+ break;
+
+ default:
+ throw new Exception("Unknown RFB message type " + msgType);
+ }
+ }
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ Log.v(TAG, "Closing VNC Connection");
+ rfb.close();
+ }
+ }
+
+ /**
+ * Apply scroll offset and scaling to convert touch-space coordinates to the corresponding
+ * point on the full frame.
+ * @param e MotionEvent with the original, touch space coordinates. This event is altered in place.
+ * @return e -- The same event passed in, with the coordinates mapped
+ */
+ MotionEvent changeTouchCoordinatesToFullFrame(MotionEvent e)
+ {
+ //Log.v(TAG, String.format("tap at %f,%f", e.getX(), e.getY()));
+ float scale = getScale();
+
+ // Adjust coordinates for Android notification bar.
+ e.offsetLocation(0, -1f * getTop());
+
+ e.setLocation(absoluteXPosition + e.getX() / scale, absoluteYPosition + e.getY() / scale);
+
+ return e;
+ }
+
+ public void onDestroy() {
+ Log.v(TAG, "Cleaning up resources");
+ if ( bitmapData!=null) bitmapData.dispose();
+ bitmapData = null;
+ }
+
+ /**
+ * Warp the mouse to x, y in the RFB coordinates
+ * @param x
+ * @param y
+ */
+ void warpMouse(int x, int y)
+ {
+ bitmapData.invalidateMousePosition();
+ mouseX=x;
+ mouseY=y;
+ bitmapData.invalidateMousePosition();
+ try
+ {
+ rfb.writePointerEvent(x, y, 0, MOUSE_BUTTON_NONE);
+ }
+ catch ( IOException ioe)
+ {
+ Log.w(TAG,ioe);
+ }
+ }
+
+ /*
+ * f(x,s) is a function that returns the coordinate in screen/scroll space corresponding
+ * to the coordinate x in full-frame space with scaling s.
+ *
+ * This function returns the difference between f(x,s1) and f(x,s2)
+ *
+ * f(x,s) = (x - i/2) * s + ((i - w)/2)) * s
+ * = s (x - i/2 + i/2 + w/2)
+ * = s (x + w/2)
+ *
+ *
+ * f(x,s) = (x - ((i - w)/2)) * s
+ * @param oldscaling
+ * @param scaling
+ * @param imageDim
+ * @param windowDim
+ * @param offset
+ * @return
+ */
+
+ /**
+ * Change to Canvas's scroll position to match the absoluteXPosition
+ */
+ void scrollToAbsolute()
+ {
+ float scale = getScale();
+ scrollTo((int)((absoluteXPosition + ((float)getWidth() - getImageWidth()) / 2 ) * scale),
+ (int)((absoluteYPosition + ((float)getHeight() - getImageHeight()) / 2 ) * scale));
+ }
+
+ /**
+ * Make sure mouse is visible on displayable part of screen
+ */
+ void panToMouse()
+ {
+ if (! connection.getFollowMouse())
+ return;
+
+ if (scaling != null && ! scaling.isAbleToPan())
+ return;
+
+ int x = mouseX;
+ int y = mouseY;
+ boolean panned = false;
+ int w = getVisibleWidth();
+ int h = getVisibleHeight();
+ int iw = getImageWidth();
+ int ih = getImageHeight();
+
+ int newX = absoluteXPosition;
+ int newY = absoluteYPosition;
+
+ if (x - newX >= w - 5)
+ {
+ newX = x - w + 5;
+ if (newX + w > iw)
+ newX = iw - w;
+ }
+ else if (x < newX + 5)
+ {
+ newX = x - 5;
+ if (newX < 0)
+ newX = 0;
+ }
+ if ( newX != absoluteXPosition ) {
+ absoluteXPosition = newX;
+ panned = true;
+ }
+ if (y - newY >= h - 5)
+ {
+ newY = y - h + 5;
+ if (newY + h > ih)
+ newY = ih - h;
+ }
+ else if (y < newY + 5)
+ {
+ newY = y - 5;
+ if (newY < 0)
+ newY = 0;
+ }
+ if ( newY != absoluteYPosition ) {
+ absoluteYPosition = newY;
+ panned = true;
+ }
+ if (panned)
+ {
+ scrollToAbsolute();
+ }
+ }
+
+ /**
+ * Pan by a number of pixels (relative pan)
+ * @param dX
+ * @param dY
+ * @return True if the pan changed the view (did not move view out of bounds); false otherwise
+ */
+ boolean pan(int dX, int dY) {
+
+ double scale = getScale();
+
+ double sX = (double)dX / scale;
+ double sY = (double)dY / scale;
+
+ if (absoluteXPosition + sX < 0)
+ // dX = diff to 0
+ sX = -absoluteXPosition;
+ if (absoluteYPosition + sY < 0)
+ sY = -absoluteYPosition;
+
+ // Prevent panning right or below desktop image
+ if (absoluteXPosition + getVisibleWidth() + sX > getImageWidth())
+ sX = getImageWidth() - getVisibleWidth() - absoluteXPosition;
+ if (absoluteYPosition + getVisibleHeight() + sY > getImageHeight())
+ sY = getImageHeight() - getVisibleHeight() - absoluteYPosition;
+
+ absoluteXPosition += sX;
+ absoluteYPosition += sY;
+ if (sX != 0.0 || sY != 0.0)
+ {
+ scrollToAbsolute();
+ return true;
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see android.view.View#onScrollChanged(int, int, int, int)
+ */
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ bitmapData.scrollChanged(absoluteXPosition, absoluteYPosition);
+ mouseFollowPan();
+ }
+
+ void handleRawRect(int x, int y, int w, int h) throws IOException {
+ handleRawRect(x, y, w, h, true);
+ }
+
+ byte[] handleRawRectBuffer = new byte[128];
+ void handleRawRect(int x, int y, int w, int h, boolean paint) throws IOException {
+ boolean valid=bitmapData.validDraw(x, y, w, h);
+ int[] pixels=bitmapData.bitmapPixels;
+ if (bytesPerPixel == 1) {
+ // 1 byte per pixel. Use palette lookup table.
+ if (w > handleRawRectBuffer.length) {
+ handleRawRectBuffer = new byte[w];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ rfb.readFully(handleRawRectBuffer, 0, w);
+ if ( ! valid)
+ continue;
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ pixels[offset + i] = colorPalette[0xFF & handleRawRectBuffer[i]];
+ }
+ }
+ } else {
+ // 4 bytes per pixel (argb) 24-bit color
+
+ final int l = w * 4;
+ if (l>handleRawRectBuffer.length) {
+ handleRawRectBuffer = new byte[l];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ rfb.readFully(handleRawRectBuffer, 0, l);
+ if ( ! valid)
+ continue;
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ final int idx = i*4;
+ pixels[offset + i] = // 0xFF << 24 |
+ (handleRawRectBuffer[idx + 2] & 0xff) << 16 | (handleRawRectBuffer[idx + 1] & 0xff) << 8 | (handleRawRectBuffer[idx] & 0xff);
+ }
+ }
+ }
+
+ if ( ! valid)
+ return;
+
+ bitmapData.updateBitmap( x, y, w, h);
+
+ if (paint)
+ reDraw();
+ }
+
+ private Runnable reDraw = new Runnable() {
+ public void run() {
+ if (showDesktopInfo) {
+ // Show a Toast with the desktop info on first frame draw.
+ showDesktopInfo = false;
+ showConnectionInfo();
+ }
+ if (bitmapData != null)
+ bitmapData.updateView(VncCanvas.this);
+ }
+ };
+
+ private void reDraw() {
+ if (repaintsEnabled)
+ handler.post(reDraw);
+ }
+
+ public void disableRepaints() {
+ repaintsEnabled = false;
+ }
+
+ public void enableRepaints() {
+ repaintsEnabled = true;
+ }
+
+ public void showConnectionInfo() {
+ String msg = rfb.desktopName;
+ int idx = rfb.desktopName.indexOf("(");
+ if (idx > -1) {
+ // Breakup actual desktop name from IP addresses for improved
+ // readability
+ String dn = rfb.desktopName.substring(0, idx).trim();
+ String ip = rfb.desktopName.substring(idx).trim();
+ msg = dn + "\n" + ip;
+ }
+ msg += "\n" + rfb.framebufferWidth + "x" + rfb.framebufferHeight;
+ String enc = getEncoding();
+ // Encoding might not be set when we display this message
+ if (enc != null && !enc.equals(""))
+ msg += ", " + getEncoding() + " encoding, " + colorModel.toString();
+ else
+ msg += ", " + colorModel.toString();
+ Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
+ }
+
+ private String getEncoding() {
+ switch (preferredEncoding) {
+ case RfbProto.EncodingRaw:
+ return "RAW";
+ case RfbProto.EncodingTight:
+ return "TIGHT";
+ case RfbProto.EncodingCoRRE:
+ return "CoRRE";
+ case RfbProto.EncodingHextile:
+ return "HEXTILE";
+ case RfbProto.EncodingRRE:
+ return "RRE";
+ case RfbProto.EncodingZlib:
+ return "ZLIB";
+ case RfbProto.EncodingZRLE:
+ return "ZRLE";
+ }
+ return "";
+ }
+
+ // Useful shortcuts for modifier masks.
+
+ final static int CTRL_MASK = KeyEvent.META_SYM_ON;
+ final static int SHIFT_MASK = KeyEvent.META_SHIFT_ON;
+ final static int META_MASK = 0;
+ final static int ALT_MASK = KeyEvent.META_ALT_ON;
+
+ private static final int MOUSE_BUTTON_NONE = 0;
+ static final int MOUSE_BUTTON_LEFT = 1;
+ static final int MOUSE_BUTTON_MIDDLE = 2;
+ static final int MOUSE_BUTTON_RIGHT = 4;
+ static final int MOUSE_BUTTON_SCROLL_UP = 8;
+ static final int MOUSE_BUTTON_SCROLL_DOWN = 16;
+
+ /**
+ * Current state of "mouse" buttons
+ * Alt meta means use second mouse button
+ * 0 = none
+ * 1 = default button
+ * 2 = second button
+ */
+ private int pointerMask = MOUSE_BUTTON_NONE;
+
+ /**
+ * Convert a motion event to a format suitable for sending over the wire
+ * @param evt motion event; x and y must already have been converted from screen coordinates
+ * to remote frame buffer coordinates. cameraButton flag is interpreted as second mouse
+ * button
+ * @param downEvent True if "mouse button" (touch or trackball button) is down when this happens
+ * @return true if event was actually sent
+ */
+ public boolean processPointerEvent(MotionEvent evt,boolean downEvent)
+ {
+ return processPointerEvent(evt,downEvent,cameraButtonDown);
+ }
+
+ /**
+ * Convert a motion event to a format suitable for sending over the wire
+ * @param evt motion event; x and y must already have been converted from screen coordinates
+ * to remote frame buffer coordinates.
+ * @param downEvent True if "mouse button" (touch or trackball button) is down when this happens
+ * @param useRightButton If true, event is interpreted as happening with right mouse button
+ * @return true if event was actually sent
+ */
+ public boolean processPointerEvent(MotionEvent evt,boolean downEvent,boolean useRightButton) {
+ return processPointerEvent((int)evt.getX(),(int)evt.getY(), evt.getAction(), evt.getMetaState(), downEvent, useRightButton);
+ }
+
+ boolean processPointerEvent(int x, int y, int action, int modifiers, boolean mouseIsDown, boolean useRightButton) {
+ if (rfb != null && rfb.inNormalProtocol) {
+ if (action == MotionEvent.ACTION_DOWN || (mouseIsDown && action == MotionEvent.ACTION_MOVE)) {
+ if (useRightButton) {
+ pointerMask = MOUSE_BUTTON_RIGHT;
+ } else {
+ pointerMask = MOUSE_BUTTON_LEFT;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ pointerMask = 0;
+ }
+ bitmapData.invalidateMousePosition();
+ mouseX= x;
+ mouseY= y;
+ if ( mouseX<0) mouseX=0;
+ else if ( mouseX>=rfb.framebufferWidth) mouseX=rfb.framebufferWidth-1;
+ if ( mouseY<0) mouseY=0;
+ else if ( mouseY>=rfb.framebufferHeight) mouseY=rfb.framebufferHeight-1;
+ bitmapData.invalidateMousePosition();
+ try {
+ rfb.writePointerEvent(mouseX,mouseY,modifiers,pointerMask);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ panToMouse();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Moves the scroll while the volume key is held down
+ * @author Michael A. MacDonald
+ */
+ class MouseScrollRunnable implements Runnable
+ {
+ int delay = 100;
+
+ int scrollButton = 0;
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ try
+ {
+ rfb.writePointerEvent(mouseX, mouseY, 0, scrollButton);
+ rfb.writePointerEvent(mouseX, mouseY, 0, 0);
+
+ handler.postDelayed(this, delay);
+ }
+ catch (IOException ioe)
+ {
+
+ }
+ }
+ }
+
+ public boolean processLocalKeyEvent(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_MENU)
+ // Ignore menu key
+ return true;
+ if (keyCode == KeyEvent.KEYCODE_CAMERA)
+ {
+ cameraButtonDown = (evt.getAction() != KeyEvent.ACTION_UP);
+ }
+ else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)
+ {
+ int mouseChange = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? MOUSE_BUTTON_SCROLL_DOWN : MOUSE_BUTTON_SCROLL_UP;
+ if (evt.getAction() == KeyEvent.ACTION_DOWN)
+ {
+ // If not auto-repeat
+ if (scrollRunnable.scrollButton != mouseChange)
+ {
+ pointerMask |= mouseChange;
+ scrollRunnable.scrollButton = mouseChange;
+ handler.postDelayed(scrollRunnable,200);
+ }
+ }
+ else
+ {
+ handler.removeCallbacks(scrollRunnable);
+ scrollRunnable.scrollButton = 0;
+ pointerMask &= ~mouseChange;
+ }
+ try
+ {
+ rfb.writePointerEvent(mouseX, mouseY, evt.getMetaState(), pointerMask);
+ }
+ catch (IOException ioe)
+ {
+ // TODO: do something with exception
+ }
+ return true;
+ }
+ if (rfb != null && rfb.inNormalProtocol) {
+ boolean down = (evt.getAction() == KeyEvent.ACTION_DOWN);
+ int key;
+ int metaState = evt.getMetaState();
+
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_BACK : key = 0xff1b; break;
+ case KeyEvent.KEYCODE_DPAD_LEFT: key = 0xff51; break;
+ case KeyEvent.KEYCODE_DPAD_UP: key = 0xff52; break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT: key = 0xff53; break;
+ case KeyEvent.KEYCODE_DPAD_DOWN: key = 0xff54; break;
+ case KeyEvent.KEYCODE_DEL: key = 0xff08; break;
+ case KeyEvent.KEYCODE_ENTER: key = 0xff0d; break;
+ case KeyEvent.KEYCODE_DPAD_CENTER: key = 0xff0d; break;
+ default:
+ key = evt.getUnicodeChar();
+ metaState = 0;
+ break;
+ }
+ try {
+ if (afterMenu)
+ {
+ afterMenu = false;
+ if (!down && key != lastKeyDown)
+ return true;
+ }
+ if (down)
+ lastKeyDown = key;
+ //Log.i(TAG,"key = " + key + " metastate = " + metaState + " keycode = " + keyCode);
+ rfb.writeKeyEvent(key, metaState, down);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void closeConnection() {
+ maintainConnection = false;
+ }
+
+ void sendMetaKey(MetaKeyBean meta)
+ {
+ if (meta.isMouseClick())
+ {
+ try {
+ rfb.writePointerEvent(mouseX, mouseY, meta.getMetaFlags(), meta.getMouseButtons());
+ rfb.writePointerEvent(mouseX, mouseY, meta.getMetaFlags(), 0);
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+ else {
+ try {
+ rfb.writeKeyEvent(meta.getKeySym(), meta.getMetaFlags(), true);
+ rfb.writeKeyEvent(meta.getKeySym(), meta.getMetaFlags(), false);
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ float getScale()
+ {
+ if (scaling == null)
+ return 1;
+ return scaling.getScale();
+ }
+
+ public int getVisibleWidth() {
+ return (int)((double)getWidth() / getScale() + 0.5);
+ }
+
+ public int getVisibleHeight() {
+ return (int)((double)getHeight() / getScale() + 0.5);
+ }
+
+ public int getImageWidth() {
+ return bitmapData.framebufferwidth;
+ }
+
+ public int getImageHeight() {
+ return bitmapData.framebufferheight;
+ }
+
+ public int getCenteredXOffset() {
+ int xoffset = (bitmapData.framebufferwidth - getWidth()) / 2;
+ return xoffset;
+ }
+
+ public int getCenteredYOffset() {
+ int yoffset = (bitmapData.framebufferheight - getHeight()) / 2;
+ return yoffset;
+ }
+
+ /**
+ * Additional Encodings
+ *
+ */
+
+ private void setEncodings(boolean autoSelectOnly) {
+ if (rfb == null || !rfb.inNormalProtocol)
+ return;
+
+ if (preferredEncoding == -1) {
+ // Preferred format is ZRLE
+ preferredEncoding = RfbProto.EncodingZRLE;
+ } else {
+ // Auto encoder selection is not enabled.
+ if (autoSelectOnly)
+ return;
+ }
+
+ int[] encodings = new int[20];
+ int nEncodings = 0;
+
+ encodings[nEncodings++] = preferredEncoding;
+ if (useCopyRect)
+ encodings[nEncodings++] = RfbProto.EncodingCopyRect;
+ // if (preferredEncoding != RfbProto.EncodingTight)
+ // encodings[nEncodings++] = RfbProto.EncodingTight;
+ if (preferredEncoding != RfbProto.EncodingZRLE)
+ encodings[nEncodings++] = RfbProto.EncodingZRLE;
+ if (preferredEncoding != RfbProto.EncodingHextile)
+ encodings[nEncodings++] = RfbProto.EncodingHextile;
+ if (preferredEncoding != RfbProto.EncodingZlib)
+ encodings[nEncodings++] = RfbProto.EncodingZlib;
+ if (preferredEncoding != RfbProto.EncodingCoRRE)
+ encodings[nEncodings++] = RfbProto.EncodingCoRRE;
+ if (preferredEncoding != RfbProto.EncodingRRE)
+ encodings[nEncodings++] = RfbProto.EncodingRRE;
+
+ if (compressLevel >= 0 && compressLevel <= 9)
+ encodings[nEncodings++] = RfbProto.EncodingCompressLevel0 + compressLevel;
+ if (jpegQuality >= 0 && jpegQuality <= 9)
+ encodings[nEncodings++] = RfbProto.EncodingQualityLevel0 + jpegQuality;
+
+ if (requestCursorUpdates) {
+ encodings[nEncodings++] = RfbProto.EncodingXCursor;
+ encodings[nEncodings++] = RfbProto.EncodingRichCursor;
+ if (!ignoreCursorUpdates)
+ encodings[nEncodings++] = RfbProto.EncodingPointerPos;
+ }
+
+ encodings[nEncodings++] = RfbProto.EncodingLastRect;
+ encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
+
+ boolean encodingsWereChanged = false;
+ if (nEncodings != nEncodingsSaved) {
+ encodingsWereChanged = true;
+ } else {
+ for (int i = 0; i < nEncodings; i++) {
+ if (encodings[i] != encodingsSaved[i]) {
+ encodingsWereChanged = true;
+ break;
+ }
+ }
+ }
+
+ if (encodingsWereChanged) {
+ try {
+ rfb.writeSetEncodings(encodings, nEncodings);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ encodingsSaved = encodings;
+ nEncodingsSaved = nEncodings;
+ }
+ }
+
+ //
+ // Handle a CopyRect rectangle.
+ //
+
+ final Paint handleCopyRectPaint = new Paint();
+ private void handleCopyRect(int x, int y, int w, int h) throws IOException {
+
+ /**
+ * This does not work properly yet.
+ */
+
+ rfb.readCopyRect();
+ if ( ! bitmapData.validDraw(x, y, w, h))
+ return;
+ // Source Coordinates
+ int leftSrc = rfb.copyRectSrcX;
+ int topSrc = rfb.copyRectSrcY;
+ int rightSrc = topSrc + w;
+ int bottomSrc = topSrc + h;
+
+ // Change
+ int dx = x - rfb.copyRectSrcX;
+ int dy = y - rfb.copyRectSrcY;
+
+ // Destination Coordinates
+ int leftDest = leftSrc + dx;
+ int topDest = topSrc + dy;
+ int rightDest = rightSrc + dx;
+ int bottomDest = bottomSrc + dy;
+
+ bitmapData.copyRect(new Rect(leftSrc, topSrc, rightSrc, bottomSrc), new Rect(leftDest, topDest, rightDest, bottomDest), handleCopyRectPaint);
+
+ reDraw();
+ }
+ byte[] bg_buf = new byte[4];
+ byte[] rre_buf = new byte[128];
+ //
+ // Handle an RRE-encoded rectangle.
+ //
+ private void handleRRERect(int x, int y, int w, int h) throws IOException {
+ boolean valid=bitmapData.validDraw(x, y, w, h);
+ int nSubrects = rfb.is.readInt();
+
+ rfb.readFully(bg_buf, 0, bytesPerPixel);
+ int pixel;
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & bg_buf[0]];
+ } else {
+ pixel = Color.rgb(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+ }
+ handleRREPaint.setColor(pixel);
+ if ( valid)
+ bitmapData.drawRect(x, y, w, h, handleRREPaint);
+
+ int len = nSubrects * (bytesPerPixel + 8);
+ if (len > rre_buf.length)
+ rre_buf = new byte[len];
+
+ rfb.readFully(rre_buf, 0, len);
+ if ( ! valid)
+ return;
+
+ int sx, sy, sw, sh;
+
+ int i = 0;
+ for (int j = 0; j < nSubrects; j++) {
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & rre_buf[i++]];
+ } else {
+ pixel = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
+ i += 4;
+ }
+ sx = x + ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
+ sy = y + ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
+ sw = ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
+ sh = ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
+
+ handleRREPaint.setColor(pixel);
+ bitmapData.drawRect(sx, sy, sw, sh, handleRREPaint);
+ }
+
+ reDraw();
+ }
+
+ //
+ // Handle a CoRRE-encoded rectangle.
+ //
+
+ private void handleCoRRERect(int x, int y, int w, int h) throws IOException {
+ boolean valid=bitmapData.validDraw(x, y, w, h);
+ int nSubrects = rfb.is.readInt();
+
+ rfb.readFully(bg_buf, 0, bytesPerPixel);
+ int pixel;
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & bg_buf[0]];
+ } else {
+ pixel = Color.rgb(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+ }
+ handleRREPaint.setColor(pixel);
+ if ( valid)
+ bitmapData.drawRect(x, y, w, h, handleRREPaint);
+
+ int len = nSubrects * (bytesPerPixel + 8);
+ if (len > rre_buf.length)
+ rre_buf = new byte[len];
+
+ rfb.readFully(rre_buf, 0, len);
+ if ( ! valid)
+ return;
+
+ int sx, sy, sw, sh;
+ int i = 0;
+
+ for (int j = 0; j < nSubrects; j++) {
+ if (bytesPerPixel == 1) {
+ pixel = colorPalette[0xFF & rre_buf[i++]];
+ } else {
+ pixel = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
+ i += 4;
+ }
+ sx = x + (rre_buf[i++] & 0xFF);
+ sy = y + (rre_buf[i++] & 0xFF);
+ sw = rre_buf[i++] & 0xFF;
+ sh = rre_buf[i++] & 0xFF;
+
+ handleRREPaint.setColor(pixel);
+ bitmapData.drawRect(sx, sy, sw, sh, handleRREPaint);
+ }
+
+ reDraw();
+ }
+
+ //
+ // Handle a Hextile-encoded rectangle.
+ //
+
+ // These colors should be kept between handleHextileSubrect() calls.
+ private int hextile_bg, hextile_fg;
+
+ private void handleHextileRect(int x, int y, int w, int h) throws IOException {
+
+ hextile_bg = Color.BLACK;
+ hextile_fg = Color.BLACK;
+
+ for (int ty = y; ty < y + h; ty += 16) {
+ int th = 16;
+ if (y + h - ty < 16)
+ th = y + h - ty;
+
+ for (int tx = x; tx < x + w; tx += 16) {
+ int tw = 16;
+ if (x + w - tx < 16)
+ tw = x + w - tx;
+
+ handleHextileSubrect(tx, ty, tw, th);
+ }
+
+ // Finished with a row of tiles, now let's show it.
+ reDraw();
+ }
+ }
+
+ //
+ // Handle one tile in the Hextile-encoded data.
+ //
+
+ Paint handleHextileSubrectPaint = new Paint();
+ byte[] backgroundColorBuffer = new byte[4];
+ private void handleHextileSubrect(int tx, int ty, int tw, int th) throws IOException {
+
+ int subencoding = rfb.is.readUnsignedByte();
+
+ // Is it a raw-encoded sub-rectangle?
+ if ((subencoding & RfbProto.HextileRaw) != 0) {
+ handleRawRect(tx, ty, tw, th, false);
+ return;
+ }
+
+ boolean valid=bitmapData.validDraw(tx, ty, tw, th);
+ // Read and draw the background if specified.
+ if (bytesPerPixel > backgroundColorBuffer.length) {
+ throw new RuntimeException("impossible colordepth");
+ }
+ if ((subencoding & RfbProto.HextileBackgroundSpecified) != 0) {
+ rfb.readFully(backgroundColorBuffer, 0, bytesPerPixel);
+ if (bytesPerPixel == 1) {
+ hextile_bg = colorPalette[0xFF & backgroundColorBuffer[0]];
+ } else {
+ hextile_bg = Color.rgb(backgroundColorBuffer[2] & 0xFF, backgroundColorBuffer[1] & 0xFF, backgroundColorBuffer[0] & 0xFF);
+ }
+ }
+ handleHextileSubrectPaint.setColor(hextile_bg);
+ handleHextileSubrectPaint.setStyle(Paint.Style.FILL);
+ if ( valid )
+ bitmapData.drawRect(tx, ty, tw, th, handleHextileSubrectPaint);
+
+ // Read the foreground color if specified.
+ if ((subencoding & RfbProto.HextileForegroundSpecified) != 0) {
+ rfb.readFully(backgroundColorBuffer, 0, bytesPerPixel);
+ if (bytesPerPixel == 1) {
+ hextile_fg = colorPalette[0xFF & backgroundColorBuffer[0]];
+ } else {
+ hextile_fg = Color.rgb(backgroundColorBuffer[2] & 0xFF, backgroundColorBuffer[1] & 0xFF, backgroundColorBuffer[0] & 0xFF);
+ }
+ }
+
+ // Done with this tile if there is no sub-rectangles.
+ if ((subencoding & RfbProto.HextileAnySubrects) == 0)
+ return;
+
+ int nSubrects = rfb.is.readUnsignedByte();
+ int bufsize = nSubrects * 2;
+ if ((subencoding & RfbProto.HextileSubrectsColoured) != 0) {
+ bufsize += nSubrects * bytesPerPixel;
+ }
+ if (rre_buf.length < bufsize)
+ rre_buf = new byte[bufsize];
+ rfb.readFully(rre_buf, 0, bufsize);
+
+ int b1, b2, sx, sy, sw, sh;
+ int i = 0;
+ if ((subencoding & RfbProto.HextileSubrectsColoured) == 0) {
+
+ // Sub-rectangles are all of the same color.
+ handleHextileSubrectPaint.setColor(hextile_fg);
+ for (int j = 0; j < nSubrects; j++) {
+ b1 = rre_buf[i++] & 0xFF;
+ b2 = rre_buf[i++] & 0xFF;
+ sx = tx + (b1 >> 4);
+ sy = ty + (b1 & 0xf);
+ sw = (b2 >> 4) + 1;
+ sh = (b2 & 0xf) + 1;
+ if ( valid)
+ bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
+ }
+ } else if (bytesPerPixel == 1) {
+
+ // BGR233 (8-bit color) version for colored sub-rectangles.
+ for (int j = 0; j < nSubrects; j++) {
+ hextile_fg = colorPalette[0xFF & rre_buf[i++]];
+ b1 = rre_buf[i++] & 0xFF;
+ b2 = rre_buf[i++] & 0xFF;
+ sx = tx + (b1 >> 4);
+ sy = ty + (b1 & 0xf);
+ sw = (b2 >> 4) + 1;
+ sh = (b2 & 0xf) + 1;
+ handleHextileSubrectPaint.setColor(hextile_fg);
+ if ( valid)
+ bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
+ }
+
+ } else {
+
+ // Full-color (24-bit) version for colored sub-rectangles.
+ for (int j = 0; j < nSubrects; j++) {
+ hextile_fg = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
+ i += 4;
+ b1 = rre_buf[i++] & 0xFF;
+ b2 = rre_buf[i++] & 0xFF;
+ sx = tx + (b1 >> 4);
+ sy = ty + (b1 & 0xf);
+ sw = (b2 >> 4) + 1;
+ sh = (b2 & 0xf) + 1;
+ handleHextileSubrectPaint.setColor(hextile_fg);
+ if ( valid )
+ bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
+ }
+
+ }
+ }
+
+ //
+ // Handle a ZRLE-encoded rectangle.
+ //
+
+ Paint handleZRLERectPaint = new Paint();
+ int[] handleZRLERectPalette = new int[128];
+ private void handleZRLERect(int x, int y, int w, int h) throws Exception {
+
+ if (zrleInStream == null)
+ zrleInStream = new ZlibInStream();
+
+ int nBytes = rfb.is.readInt();
+ if (nBytes > 64 * 1024 * 1024)
+ throw new Exception("ZRLE decoder: illegal compressed data size");
+
+ if (zrleBuf == null || zrleBuf.length < nBytes) {
+ zrleBuf = new byte[nBytes+4096];
+ }
+
+ rfb.readFully(zrleBuf, 0, nBytes);
+
+ zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
+
+ boolean valid=bitmapData.validDraw(x, y, w, h);
+
+ for (int ty = y; ty < y + h; ty += 64) {
+
+ int th = Math.min(y + h - ty, 64);
+
+ for (int tx = x; tx < x + w; tx += 64) {
+
+ int tw = Math.min(x + w - tx, 64);
+
+ int mode = zrleInStream.readU8();
+ boolean rle = (mode & 128) != 0;
+ int palSize = mode & 127;
+
+ readZrlePalette(handleZRLERectPalette, palSize);
+
+ if (palSize == 1) {
+ int pix = handleZRLERectPalette[0];
+ int c = (bytesPerPixel == 1) ? colorPalette[0xFF & pix] : (0xFF000000 | pix);
+ handleZRLERectPaint.setColor(c);
+ handleZRLERectPaint.setStyle(Paint.Style.FILL);
+ if ( valid)
+ bitmapData.drawRect(tx, ty, tw, th, handleZRLERectPaint);
+ continue;
+ }
+
+ if (!rle) {
+ if (palSize == 0) {
+ readZrleRawPixels(tw, th);
+ } else {
+ readZrlePackedPixels(tw, th, handleZRLERectPalette, palSize);
+ }
+ } else {
+ if (palSize == 0) {
+ readZrlePlainRLEPixels(tw, th);
+ } else {
+ readZrlePackedRLEPixels(tw, th, handleZRLERectPalette);
+ }
+ }
+ if ( valid )
+ handleUpdatedZrleTile(tx, ty, tw, th);
+ }
+ }
+
+ zrleInStream.reset();
+
+ reDraw();
+ }
+
+ //
+ // Handle a Zlib-encoded rectangle.
+ //
+
+ byte[] handleZlibRectBuffer = new byte[128];
+ private void handleZlibRect(int x, int y, int w, int h) throws Exception {
+ boolean valid = bitmapData.validDraw(x, y, w, h);
+ int nBytes = rfb.is.readInt();
+
+ if (zlibBuf == null || zlibBuf.length < nBytes) {
+ zlibBuf = new byte[nBytes*2];
+ }
+
+ rfb.readFully(zlibBuf, 0, nBytes);
+
+ if (zlibInflater == null) {
+ zlibInflater = new Inflater();
+ }
+ zlibInflater.setInput(zlibBuf, 0, nBytes);
+
+ int[] pixels=bitmapData.bitmapPixels;
+
+ if (bytesPerPixel == 1) {
+ // 1 byte per pixel. Use palette lookup table.
+ if (w > handleZlibRectBuffer.length) {
+ handleZlibRectBuffer = new byte[w];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ zlibInflater.inflate(handleZlibRectBuffer, 0, w);
+ if ( ! valid)
+ continue;
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ pixels[offset + i] = colorPalette[0xFF & handleZlibRectBuffer[i]];
+ }
+ }
+ } else {
+ // 24-bit color (ARGB) 4 bytes per pixel.
+ final int l = w*4;
+ if (l > handleZlibRectBuffer.length) {
+ handleZlibRectBuffer = new byte[l];
+ }
+ int i, offset;
+ for (int dy = y; dy < y + h; dy++) {
+ zlibInflater.inflate(handleZlibRectBuffer, 0, l);
+ if ( ! valid)
+ continue;
+ offset = bitmapData.offset(x, dy);
+ for (i = 0; i < w; i++) {
+ final int idx = i*4;
+ pixels[offset + i] = (handleZlibRectBuffer[idx + 2] & 0xFF) << 16 | (handleZlibRectBuffer[idx + 1] & 0xFF) << 8 | (handleZlibRectBuffer[idx] & 0xFF);
+ }
+ }
+ }
+ if ( ! valid)
+ return;
+ bitmapData.updateBitmap(x, y, w, h);
+
+ reDraw();
+ }
+
+ private int readPixel(InStream is) throws Exception {
+ int pix;
+ if (bytesPerPixel == 1) {
+ pix = is.readU8();
+ } else {
+ int p1 = is.readU8();
+ int p2 = is.readU8();
+ int p3 = is.readU8();
+ pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
+ }
+ return pix;
+ }
+
+ byte[] readPixelsBuffer = new byte[128];
+ private void readPixels(InStream is, int[] dst, int count) throws Exception {
+ if (bytesPerPixel == 1) {
+ if (count > readPixelsBuffer.length) {
+ readPixelsBuffer = new byte[count];
+ }
+ is.readBytes(readPixelsBuffer, 0, count);
+ for (int i = 0; i < count; i++) {
+ dst[i] = (int) readPixelsBuffer[i] & 0xFF;
+ }
+ } else {
+ final int l = count * 3;
+ if (l > readPixelsBuffer.length) {
+ readPixelsBuffer = new byte[l];
+ }
+ is.readBytes(readPixelsBuffer, 0, l);
+ for (int i = 0; i < count; i++) {
+ final int idx = i*3;
+ dst[i] = ((readPixelsBuffer[idx + 2] & 0xFF) << 16 | (readPixelsBuffer[idx + 1] & 0xFF) << 8 | (readPixelsBuffer[idx] & 0xFF));
+ }
+ }
+ }
+
+ private void readZrlePalette(int[] palette, int palSize) throws Exception {
+ readPixels(zrleInStream, palette, palSize);
+ }
+
+ private void readZrleRawPixels(int tw, int th) throws Exception {
+ int len = tw * th;
+ if (zrleTilePixels == null || len > zrleTilePixels.length)
+ zrleTilePixels = new int[len];
+ readPixels(zrleInStream, zrleTilePixels, tw * th); // /
+ }
+
+ private void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) throws Exception {
+
+ int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
+ int ptr = 0;
+ int len = tw * th;
+ if (zrleTilePixels == null || len > zrleTilePixels.length)
+ zrleTilePixels = new int[len];
+
+ for (int i = 0; i < th; i++) {
+ int eol = ptr + tw;
+ int b = 0;
+ int nbits = 0;
+
+ while (ptr < eol) {
+ if (nbits == 0) {
+ b = zrleInStream.readU8();
+ nbits = 8;
+ }
+ nbits -= bppp;
+ int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
+ if (bytesPerPixel == 1) {
+ if (index >= colorPalette.length)
+ Log.e(TAG, "zrlePlainRLEPixels palette lookup out of bounds " + index + " (0x" + Integer.toHexString(index) + ")");
+ zrleTilePixels[ptr++] = colorPalette[0xFF & palette[index]];
+ } else {
+ zrleTilePixels[ptr++] = palette[index];
+ }
+ }
+ }
+ }
+
+ private void readZrlePlainRLEPixels(int tw, int th) throws Exception {
+ int ptr = 0;
+ int end = ptr + tw * th;
+ if (zrleTilePixels == null || end > zrleTilePixels.length)
+ zrleTilePixels = new int[end];
+ while (ptr < end) {
+ int pix = readPixel(zrleInStream);
+ int len = 1;
+ int b;
+ do {
+ b = zrleInStream.readU8();
+ len += b;
+ } while (b == 255);
+
+ if (!(len <= end - ptr))
+ throw new Exception("ZRLE decoder: assertion failed" + " (len <= end-ptr)");
+
+ if (bytesPerPixel == 1) {
+ while (len-- > 0)
+ zrleTilePixels[ptr++] = colorPalette[0xFF & pix];
+ } else {
+ while (len-- > 0)
+ zrleTilePixels[ptr++] = pix;
+ }
+ }
+ }
+
+ private void readZrlePackedRLEPixels(int tw, int th, int[] palette) throws Exception {
+
+ int ptr = 0;
+ int end = ptr + tw * th;
+ if (zrleTilePixels == null || end > zrleTilePixels.length)
+ zrleTilePixels = new int[end];
+ while (ptr < end) {
+ int index = zrleInStream.readU8();
+ int len = 1;
+ if ((index & 128) != 0) {
+ int b;
+ do {
+ b = zrleInStream.readU8();
+ len += b;
+ } while (b == 255);
+
+ if (!(len <= end - ptr))
+ throw new Exception("ZRLE decoder: assertion failed" + " (len <= end - ptr)");
+ }
+
+ index &= 127;
+ int pix = palette[index];
+
+ if (bytesPerPixel == 1) {
+ while (len-- > 0)
+ zrleTilePixels[ptr++] = colorPalette[0xFF & pix];
+ } else {
+ while (len-- > 0)
+ zrleTilePixels[ptr++] = pix;
+ }
+ }
+ }
+
+ //
+ // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
+ //
+
+ private void handleUpdatedZrleTile(int x, int y, int w, int h) {
+ int offsetSrc = 0;
+ int[] destPixels=bitmapData.bitmapPixels;
+ for (int j = 0; j < h; j++) {
+ System.arraycopy(zrleTilePixels, offsetSrc, destPixels, bitmapData.offset(x, y + j), w);
+ offsetSrc += w;
+ }
+
+ bitmapData.updateBitmap(x, y, w, h);
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/VncCanvasActivity.java b/app/src/main/java/android/androidVNC/VncCanvasActivity.java
new file mode 100644
index 000000000..654cbd025
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncCanvasActivity.java
@@ -0,0 +1,1917 @@
+/*
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// CanvasView is the Activity for showing VNC Desktop.
+//
+package android.androidVNC;
+
+import android.app.*;
+import android.content.*;
+import android.content.DialogInterface.*;
+import android.content.res.*;
+import android.database.*;
+import android.graphics.*;
+import android.net.*;
+import android.os.*;
+import android.support.design.widget.*;
+import android.support.v4.widget.*;
+import android.support.v7.app.*;
+import android.util.*;
+import android.view.*;
+import android.widget.*;
+import android.widget.AdapterView.*;
+import com.antlersoft.android.bc.*;
+import com.theqvd.android.xpro.*;
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import net.kdt.pojavlaunch.*;
+import net.kdt.pojavlaunch.prefs.*;
+
+import android.app.AlertDialog;
+import com.theqvd.android.xpro.Config;
+
+public class VncCanvasActivity extends AppCompatActivity
+{
+
+ private NavigationView navDrawer;
+
+ private DrawerLayout drawerLayout;
+
+ /**
+ * @author Michael A. MacDonald
+ */
+ class ZoomInputHandler extends AbstractGestureInputHandler {
+
+ /**
+ * In drag mode (entered with long press) you process mouse events
+ * without sending them through the gesture detector
+ */
+ private boolean dragMode;
+
+ /**
+ * Key handler delegate that handles DPad-based mouse motion
+ */
+ private DPadMouseKeyHandler keyHandler;
+
+ /**
+ * @param c
+ */
+ ZoomInputHandler() {
+ super(VncCanvasActivity.this);
+ keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this,vncCanvas.handler);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getHandlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getString(
+ R.string.input_mode_touch_pan_zoom_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return TOUCH_ZOOM_MODE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onDown(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onDown(MotionEvent e) {
+ panner.stop();
+ return true;
+ }
+
+ /**
+ * Divide stated fling velocity by this amount to get initial velocity
+ * per pan interval
+ */
+ static final float FLING_FACTOR = 8;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onFling(android.view.MotionEvent,
+ * android.view.MotionEvent, float, float)
+ */
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ showZoomer(false);
+ panner.start(-(velocityX / FLING_FACTOR),
+ -(velocityY / FLING_FACTOR), new Panner.VelocityUpdater() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.Panner.VelocityUpdater#updateVelocity(android.graphics.Point,
+ * long)
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ double scale = Math.pow(0.8, interval / 50.0);
+ p.x *= scale;
+ p.y *= scale;
+ return (Math.abs(p.x) > 0.5 || Math.abs(p.y) > 0.5);
+ }
+
+ });
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractGestureInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (dragMode) {
+ vncCanvas.changeTouchCoordinatesToFullFrame(e);
+ if (e.getAction() == MotionEvent.ACTION_UP)
+ dragMode = false;
+ return vncCanvas.processPointerEvent(e, true);
+ } else
+ return super.onTouchEvent(e);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onLongPress(android.view.MotionEvent)
+ */
+ @Override
+ public void onLongPress(MotionEvent e) {
+ showZoomer(true);
+ BCFactory.getInstance().getBCHaptic().performLongPressHaptic(
+ vncCanvas);
+ dragMode = true;
+ vncCanvas.processPointerEvent(vncCanvas
+ .changeTouchCoordinatesToFullFrame(e), true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onScroll(android.view.MotionEvent,
+ * android.view.MotionEvent, float, float)
+ */
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ if (inScaling)
+ return false;
+ showZoomer(false);
+ return vncCanvas.pan((int) distanceX, (int) distanceY);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onSingleTapConfirmed(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ vncCanvas.changeTouchCoordinatesToFullFrame(e);
+ vncCanvas.processPointerEvent(e, true);
+ e.setAction(MotionEvent.ACTION_UP);
+ return vncCanvas.processPointerEvent(e, false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onDoubleTap(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ vncCanvas.changeTouchCoordinatesToFullFrame(e);
+ vncCanvas.processPointerEvent(e, true, true);
+ e.setAction(MotionEvent.ACTION_UP);
+ return vncCanvas.processPointerEvent(e, false, true);
+ }
+
+ }
+
+ public class TouchpadInputHandler extends AbstractGestureInputHandler {
+ /**
+ * In drag mode (entered with long press) you process mouse events
+ * without sending them through the gesture detector
+ */
+ private boolean dragMode;
+ float dragX, dragY;
+
+ /**
+ * Key handler delegate that handles DPad-based mouse motion
+ */
+ private DPadMouseKeyHandler keyHandler;
+
+ TouchpadInputHandler() {
+ super(VncCanvasActivity.this);
+ keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this,vncCanvas.handler);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getHandlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getString(
+ R.string.input_mode_touchpad);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ // TOUCHPAD_MODE is default
+ return TOUCHPAD_MODE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.VncCanvasActivity.ZoomInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /**
+ * scale down delta when it is small. This will allow finer control
+ * when user is making a small movement on touch screen.
+ * Scale up delta when delta is big. This allows fast mouse movement when
+ * user is flinging.
+ * @param deltaX
+ * @return
+ */
+ private float fineCtrlScale(float delta) {
+ float sign = (delta>0) ? 1 : -1;
+ delta = Math.abs(delta);
+ if (delta>=1 && delta <=3) {
+ delta = 1;
+ }else if (delta <= 10) {
+ delta *= 0.34;
+ } else if (delta <= 30 ) {
+ delta *= delta/30;
+ } else if (delta <= 90) {
+ delta *= (delta/30);
+ } else {
+ delta *= 3.0;
+ }
+ return sign * delta;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onLongPress(android.view.MotionEvent)
+ */
+ @Override
+ public void onLongPress(MotionEvent e) {
+
+
+ showZoomer(true);
+ BCFactory.getInstance().getBCHaptic().performLongPressHaptic(
+ vncCanvas);
+ dragMode = true;
+ dragX = e.getX();
+ dragY = e.getY();
+ // send a mouse down event to the remote without moving the mouse.
+ remoteMouseStayPut(e);
+ vncCanvas.processPointerEvent(e, true);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onScroll(android.view.MotionEvent,
+ * android.view.MotionEvent, float, float)
+ */
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+
+ if (BCFactory.getInstance().getBCMotionEvent().getPointerCount(e2) > 1)
+ {
+ if (inScaling)
+ return false;
+ showZoomer(false);
+ return vncCanvas.pan((int) distanceX, (int) distanceY);
+ }
+ else
+ {
+ // compute the relative movement offset on the remote screen.
+ float deltaX = -distanceX *vncCanvas.getScale();
+ float deltaY = -distanceY *vncCanvas.getScale();
+ deltaX = fineCtrlScale(deltaX);
+ deltaY = fineCtrlScale(deltaY);
+
+ // compute the absolution new mouse pos on the remote site.
+ float newRemoteX = vncCanvas.mouseX + deltaX;
+ float newRemoteY = vncCanvas.mouseY + deltaY;
+
+
+ if (dragMode) {
+ if (e2.getAction() == MotionEvent.ACTION_UP)
+ dragMode = false;
+ dragX = e2.getX();
+ dragY = e2.getY();
+ e2.setLocation(newRemoteX, newRemoteY);
+ return vncCanvas.processPointerEvent(e2, true);
+ } else {
+ e2.setLocation(newRemoteX, newRemoteY);
+ vncCanvas.processPointerEvent(e2, false);
+ }
+ }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractGestureInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (dragMode) {
+ // compute the relative movement offset on the remote screen.
+ float deltaX = (e.getX() - dragX) *vncCanvas.getScale();
+ float deltaY = (e.getY() - dragY) *vncCanvas.getScale();
+ dragX = e.getX();
+ dragY = e.getY();
+ deltaX = fineCtrlScale(deltaX);
+ deltaY = fineCtrlScale(deltaY);
+
+ // compute the absolution new mouse pos on the remote site.
+ float newRemoteX = vncCanvas.mouseX + deltaX;
+ float newRemoteY = vncCanvas.mouseY + deltaY;
+
+
+ if (e.getAction() == MotionEvent.ACTION_UP)
+ dragMode = false;
+ e.setLocation(newRemoteX, newRemoteY);
+ return vncCanvas.processPointerEvent(e, true);
+ } else
+ return super.onTouchEvent(e);
+ }
+
+ /**
+ * Modify the event so that it does not move the mouse on the
+ * remote server.
+ * @param e
+ */
+ private void remoteMouseStayPut(MotionEvent e) {
+ e.setLocation(vncCanvas.mouseX, vncCanvas.mouseY);
+
+ }
+ /*
+ * (non-Javadoc)
+ * confirmed single tap: do a single mouse click on remote without moving the mouse.
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onSingleTapConfirmed(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ boolean multiTouch = (BCFactory.getInstance().getBCMotionEvent().getPointerCount(e) > 1);
+ remoteMouseStayPut(e);
+
+ vncCanvas.processPointerEvent(e, true, multiTouch||vncCanvas.cameraButtonDown);
+ e.setAction(MotionEvent.ACTION_UP);
+ return vncCanvas.processPointerEvent(e, false, multiTouch||vncCanvas.cameraButtonDown);
+ }
+
+ /*
+ * (non-Javadoc)
+ * double tap: do two left mouse right mouse clicks on remote without moving the mouse.
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onDoubleTap(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ remoteMouseStayPut(e);
+ vncCanvas.processPointerEvent(e, true, true);
+ e.setAction(MotionEvent.ACTION_UP);
+ return vncCanvas.processPointerEvent(e, false, true);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.GestureDetector.SimpleOnGestureListener#onDown(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onDown(MotionEvent e) {
+ panner.stop();
+ return true;
+ }
+ }
+
+ private final static String TAG = "VncCanvasActivity";
+
+ AbstractInputHandler inputHandler;
+
+ VncCanvas vncCanvas;
+
+ VncDatabase database;
+
+ private MenuItem[] inputModeMenuItems;
+ private AbstractInputHandler inputModeHandlers[];
+ private ConnectionBean connection;
+ private boolean trackballButtonDown;
+ private static final int inputModeIds[] = { R.id.itemInputFitToScreen,
+ R.id.itemInputTouchpad,
+ R.id.itemInputMouse, R.id.itemInputPan,
+ R.id.itemInputTouchPanTrackballMouse,
+ R.id.itemInputDPadPanTouchMouse, R.id.itemInputTouchPanZoomMouse };
+
+ ZoomControls zoomer;
+ Panner panner;
+
+ java.lang.Process mXServerProcess;
+ MCProfile.Builder mProfile;
+ JMinecraftVersionList.Version mVersionInfo;
+ SimpleShellProcess mJavaProcess, mXVNCProcess;
+
+ private LinearLayout contentLog;
+ private TextView textLog;
+ private ScrollView contentScroll;
+ private ToggleButton toggleLog;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+
+ super.onCreate(icicle);
+
+ mProfile = PojavProfile.getCurrentProfileContent(this);
+ mVersionInfo = Tools.getVersionInfo(mProfile.getVersion());
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ setContentView(R.layout.canvas);
+
+ database = new VncDatabase(VncCanvasActivity.this);
+ connection = new ConnectionBean();
+
+ contentLog = (LinearLayout) findViewById(R.id.content_log_layout);
+ contentScroll = (ScrollView) findViewById(R.id.content_log_scroll);
+ textLog = (TextView) contentScroll.getChildAt(0);
+ toggleLog = (ToggleButton) findViewById(R.id.content_log_toggle_log);
+ toggleLog.setChecked(true);
+
+ vncCanvas = (VncCanvas) findViewById(R.id.vnc_canvas);
+ zoomer = (ZoomControls) findViewById(R.id.zoomer);
+
+ // Menu
+ drawerLayout = (DrawerLayout) findViewById(R.id.main_drawer_options);
+
+ navDrawer = (NavigationView) findViewById(R.id.main_navigation_view);
+ navDrawer.setNavigationItemSelectedListener(
+ new NavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.nav_forceclose: dialogForceClose();
+ break;
+ case R.id.nav_viewlog: openLogOutput();
+ break;
+ // case R.id.nav_debug: toggleDebug();
+ // break;
+ // case R.id.nav_customkey: dialogSendCustomKey();
+ case R.id.itemInfo:
+ vncCanvas.showConnectionInfo();
+ return true;
+ case R.id.itemSpecialKeys:
+ showDialog(R.layout.metakey);
+ return true;
+ case R.id.itemColorMode:
+ selectColorModel();
+ return true;
+ // Following sets one of the scaling options
+ case R.id.itemZoomable:
+ case R.id.itemOneToOne:
+ case R.id.itemFitToScreen:
+ AbstractScaling.getById(item.getItemId()).setScaleTypeForActivity(VncCanvasActivity.this);
+ item.setChecked(true);
+ showPanningState();
+ return true;
+ case R.id.itemCenterMouse:
+ vncCanvas.warpMouse(vncCanvas.absoluteXPosition
+ + vncCanvas.getVisibleWidth() / 2,
+ vncCanvas.absoluteYPosition + vncCanvas.getVisibleHeight()
+ / 2);
+ return true;
+ case R.id.itemDisconnect:
+ vncCanvas.closeConnection();
+ finish();
+ return true;
+ case R.id.itemEnterText:
+ showDialog(R.layout.entertext);
+ return true;
+ case R.id.itemCtrlAltDel:
+ vncCanvas.sendMetaKey(MetaKeyBean.keyCtrlAltDel);
+ return true;
+ case R.id.itemFollowMouse:
+ boolean newFollow = !connection.getFollowMouse();
+ item.setChecked(newFollow);
+ connection.setFollowMouse(newFollow);
+ if (newFollow) {
+ vncCanvas.panToMouse();
+ }
+ connection.save(database.getWritableDatabase());
+ return true;
+ case R.id.itemFollowPan:
+ boolean newFollowPan = !connection.getFollowPan();
+ item.setChecked(newFollowPan);
+ connection.setFollowPan(newFollowPan);
+ connection.save(database.getWritableDatabase());
+ return true;
+ case R.id.itemArrowLeft:
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowLeft);
+ return true;
+ case R.id.itemArrowUp:
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowUp);
+ return true;
+ case R.id.itemArrowRight:
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowRight);
+ return true;
+ case R.id.itemArrowDown:
+ vncCanvas.sendMetaKey(MetaKeyBean.keyArrowDown);
+ return true;
+ case R.id.itemSendKeyAgain:
+ sendSpecialKeyAgain();
+ return true;
+ case R.id.itemOpenDoc:
+ Utils.showDocumentation(VncCanvasActivity.this);
+ return true;
+ default:
+ AbstractInputHandler input = getInputHandlerById(item.getItemId());
+ if (input != null) {
+ inputHandler = input;
+ connection.setInputMode(input.getName());
+ if (input.getName().equals(TOUCHPAD_MODE))
+ connection.setFollowMouse(true);
+ item.setChecked(true);
+ showPanningState();
+ connection.save(database.getWritableDatabase());
+ return true;
+ }
+ }
+
+ //Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
+
+ drawerLayout.closeDrawers();
+ return true;
+ }
+ });
+ Menu menu = navDrawer.getMenu();
+ if (vncCanvas.scaling != null)
+ menu.findItem(vncCanvas.scaling.getId()).setChecked(true);
+
+ Menu inputMenu = menu.findItem(R.id.itemInputMode).getSubMenu();
+
+ inputModeMenuItems = new MenuItem[inputModeIds.length];
+ for (int i = 0; i < inputModeIds.length; i++) {
+ inputModeMenuItems[i] = inputMenu.findItem(inputModeIds[i]);
+ }
+ updateInputMenu();
+ menu.findItem(R.id.itemFollowMouse).setChecked(
+ connection.getFollowMouse());
+ menu.findItem(R.id.itemFollowPan).setChecked(connection.getFollowPan());
+
+ final Bundle extras = getIntent().getExtras();
+
+ // Launch X Server before init anything!
+ final Config config = new Config(this);
+ new Thread(new Runnable(){
+
+ @Override
+ public void run()
+ {
+ String cmd = Config.xvnccmd + " -geometry "+ config.get_width_pixels() + "x" + config.get_height_pixels();
+ cmd += config.isAppConfig_remote_vnc_allowed() ? "" : " " + Config.notAllowRemoteVncConns;
+ cmd += config.isAppConfig_render() ? " +render" : "";
+ cmd += config.isAppConfig_xinerama() ? " +xinerama" : "";
+ Log.i("VncCanvasActivity", "Launching: "+cmd);
+
+ String cmdList[] = cmd.split("[ ]+");
+ try {
+ mXVNCProcess = new SimpleShellProcess(new SimpleShellProcess.OnPrintListener(){
+
+ @Override
+ public void onPrintLine(String text)
+ {
+ log(text);
+ }
+ });
+ mXVNCProcess.initInputStream(VncCanvasActivity.this);
+ mXVNCProcess.writeToProcess(cmdList);
+
+ final String modPath;
+
+ if (extras != null) {
+ modPath = extras.getString("launchJar", "");
+ } else {
+ modPath = null;
+ }
+
+ launchJava(modPath);
+ } catch (Throwable th) {
+ Tools.showError(VncCanvasActivity.this, th);
+ }
+ }
+ }).start();
+
+ new Handler().postDelayed(new Runnable(){
+
+ @Override
+ public void run()
+ {
+
+ Uri data = extras.getParcelable("x11");
+ if ((data != null) && (data.getScheme().equals("vnc"))) {
+ String host = data.getHost();
+ // This should not happen according to Uri contract, but bug introduced in Froyo (2.2)
+ // has made this parsing of host necessary
+ int index = host.indexOf(':');
+ int port;
+ if (index != -1)
+ {
+ try
+ {
+ port = Integer.parseInt(host.substring(index + 1));
+ }
+ catch (NumberFormatException nfe)
+ {
+ port = 0;
+ }
+ host = host.substring(0,index);
+ }
+ else
+ {
+ port = data.getPort();
+ }
+ if (host.equals(VncConstants.CONNECTION))
+ {
+ if (connection.Gen_read(database.getReadableDatabase(), port))
+ {
+ MostRecentBean bean = androidVNC.getMostRecent(database.getReadableDatabase());
+ if (bean != null)
+ {
+ bean.setConnectionId(connection.get_Id());
+ bean.Gen_update(database.getWritableDatabase());
+ }
+ }
+ }
+ else
+ {
+ connection.setAddress(host);
+ connection.setNickname(connection.getAddress());
+ connection.setPort(port);
+ List path = data.getPathSegments();
+ if (path.size() >= 1) {
+ connection.setColorModel(path.get(0));
+ }
+ if (path.size() >= 2) {
+ connection.setPassword(path.get(1));
+ }
+ connection.save(database.getWritableDatabase());
+ }
+ } else {
+ if (extras != null) {
+ connection.Gen_populate((ContentValues) extras
+ .getParcelable(VncConstants.CONNECTION));
+ }
+ if (connection.getPort() == 0)
+ connection.setPort(5900);
+
+ // Parse a HOST:PORT entry
+ String host = connection.getAddress();
+ if (host.indexOf(':') > -1) {
+ String p = host.substring(host.indexOf(':') + 1);
+ try {
+ connection.setPort(Integer.parseInt(p));
+ } catch (Exception e) {
+ }
+ connection.setAddress(host.substring(0, host.indexOf(':')));
+ }
+ }
+
+ vncCanvas.initializeVncCanvas(connection, new Runnable() {
+ public void run() {
+ setModes();
+ }
+ });
+ zoomer.hide();
+ zoomer.setOnZoomInClickListener(new View.OnClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ showZoomer(true);
+ vncCanvas.scaling.zoomIn(VncCanvasActivity.this);
+
+ }
+
+ });
+ zoomer.setOnZoomOutClickListener(new View.OnClickListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View.OnClickListener#onClick(android.view.View)
+ */
+ @Override
+ public void onClick(View v) {
+ showZoomer(true);
+ vncCanvas.scaling.zoomOut(VncCanvasActivity.this);
+
+ }
+
+ });
+
+ /*
+ zoomer.setOnZoomKeyboardClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ InputMethodManager inputMgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMgr.toggleSoftInput(0, 0);
+ }
+
+ });
+ */
+ panner = new Panner(VncCanvasActivity.this, vncCanvas.handler);
+
+ inputHandler = getInputHandlerById(R.id.itemInputMouse);
+ }
+ }, 1000);
+ }
+
+ private void openLogOutput() {
+ contentLog.setVisibility(View.VISIBLE);
+ }
+
+ public void closeLogOutput(View view) {
+ contentLog.setVisibility(View.GONE);
+ }
+
+ public void dialogForceClose()
+ {
+ new AlertDialog.Builder(this)
+ .setMessage(R.string.mcn_exit_confirm)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
+
+ @Override
+ public void onClick(DialogInterface p1, int p2)
+ {
+ try {
+ mXServerProcess.destroy();
+ mJavaProcess.terminate();
+
+ System.exit(0);
+ } catch (Throwable th) {
+ Log.w(Tools.APP_NAME, "Could not enable System.exit() method!", th);
+ }
+
+ // If unable to enable exit, use method: kill self process.
+ android.os.Process.killProcess(android.os.Process.myPid());
+
+ // Toast.makeText(MainActivity.this, "Could not exit. Please force close this app.", Toast.LENGTH_LONG).show();
+ }
+ })
+ .show();
+ }
+
+ private void logn(String str) {
+ log(str + "\n");
+ }
+
+ private void log(final String str) {
+ runOnUiThread(new Runnable(){
+
+ @Override
+ public void run()
+ {
+ if (toggleLog.isChecked()) {
+ textLog.append(str);
+ contentScroll.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ }
+ });
+ }
+
+ private void launchJava(String modPath) {
+ try {
+ /*
+ * 17w43a and above change Minecraf arguments from
+ * `minecraftArguments` to `arguments` so check if
+ * selected version requires LWJGL 3 or not is easy.
+ */
+ boolean isLwjgl3 = mVersionInfo.arguments != null;
+
+ List mJreArgs = new ArrayList();
+ mJreArgs.add("java");
+ mJreArgs.add("-Duser.home=" + Tools.MAIN_PATH);
+ mJreArgs.add("-Xmx512M");
+
+ if (modPath == null) {
+ mJreArgs.add("-jar");
+ mJreArgs.add(Tools.libraries + "/ClassWrapper.jar");
+ mJreArgs.add(Tools.generate(mProfile.getVersion()));
+ mJreArgs.add(mVersionInfo.mainClass);
+ mJreArgs.addAll(Arrays.asList(getMCArgs()));
+ } else {
+ mJreArgs.add("-jar");
+ mJreArgs.add(modPath);
+ }
+
+ mJavaProcess = new SimpleShellProcess(new SimpleShellProcess.OnPrintListener(){
+ @Override
+ public void onPrintLine(String text) {
+ log(text);
+ }
+ }, LauncherPreferences.PREF_RUNASROOT ? "su" : "sh" + " " + Tools.homeJreDir + "/usr/bin/jre.sh");
+ mJavaProcess.initInputStream(this);
+ mJavaProcess.writeToProcess("unset LD_PRELOAD");
+ /* To prevent Permission Denied, chmod again.
+ * Useful if enable root mode */
+ mJavaProcess.writeToProcess("chmod -R 700 " + Tools.homeJreDir);
+ mJavaProcess.writeToProcess("cd " + Tools.MAIN_PATH);
+ mJavaProcess.writeToProcess("export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/minecraft_lib/lwjgl" + (isLwjgl3 ? "3" : "2"));
+ mJavaProcess.writeToProcess(mJreArgs.toArray(new String[0]));
+ } catch (Throwable th) {
+ th.printStackTrace();
+ Tools.showError(this, th);
+ }
+ }
+
+ private String[] getMCArgs() {
+ String username = mProfile.getUsername();
+ String versionName = mProfile.getVersion();
+ String mcAssetsDir = Tools.ASSETS_PATH;
+ String userType = "mojang";
+
+ File gameDir = new File(Tools.MAIN_PATH);
+ gameDir.mkdirs();
+
+ Map varArgMap = new ArrayMap();
+ varArgMap.put("auth_player_name", username);
+ varArgMap.put("version_name", versionName);
+ varArgMap.put("game_directory", gameDir.getAbsolutePath());
+ varArgMap.put("assets_root", mcAssetsDir);
+ varArgMap.put("assets_index_name", mVersionInfo.assets);
+ varArgMap.put("auth_uuid", mProfile.getProfileID());
+ varArgMap.put("auth_access_token", mProfile.getAccessToken());
+ varArgMap.put("user_properties", "{}");
+ varArgMap.put("user_type", userType);
+ varArgMap.put("version_type", mVersionInfo.type);
+ varArgMap.put("game_assets", Tools.ASSETS_PATH);
+
+ List minecraftArgs = new ArrayList();
+ if (mVersionInfo.arguments != null) {
+ for (Object arg : mVersionInfo.arguments.game) {
+ if (arg instanceof String) {
+ minecraftArgs.add((String) arg);
+ } else {
+ /*
+ for (JMinecraftVersionList.Arguments.ArgValue.ArgRules rule : arg.rules) {
+ // rule.action = allow
+ // TODO implement this
+ }
+ */
+ }
+ }
+ }
+
+ String[] argsFromJson = insertVariableArgument(
+ splitAndFilterEmpty(
+ mVersionInfo.minecraftArguments == null ?
+ fromStringArray(minecraftArgs.toArray(new String[0])):
+ mVersionInfo.minecraftArguments
+ ), varArgMap
+ );
+ // Tools.dialogOnUiThread(this, "Result args", Arrays.asList(argsFromJson).toString());
+ return argsFromJson;
+ }
+
+ private String fromStringArray(String[] strArr) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < strArr.length; i++) {
+ if (i > 0) builder.append(" ");
+ builder.append(strArr[i]);
+ }
+
+ return builder.toString();
+ }
+
+ private String[] splitAndFilterEmpty(String argStr) {
+ List strList = new ArrayList();
+ strList.add("--fullscreen");
+ for (String arg : argStr.split(" ")) {
+ if (!arg.isEmpty()) {
+ strList.add(arg);
+ }
+ }
+ return strList.toArray(new String[0]);
+ }
+
+ private String[] insertVariableArgument(String[] args, Map keyValueMap) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ String argVar = null;
+ if (arg.startsWith("${") && arg.endsWith("}")) {
+ argVar = arg.substring(2, arg.length() - 1);
+ for (Map.Entry keyValue : keyValueMap.entrySet()) {
+ if (argVar.equals(keyValue.getKey())) {
+ args[i] = keyValue.getValue();
+ }
+ }
+ }
+ }
+ return args;
+ }
+
+ /**
+ * Set modes on start to match what is specified in the ConnectionBean;
+ * color mode (already done) scaling, input mode
+ */
+ void setModes() {
+ AbstractInputHandler handler = getInputHandlerByName(connection
+ .getInputMode());
+ AbstractScaling.getByScaleType(connection.getScaleMode())
+ .setScaleTypeForActivity(this);
+ this.inputHandler = handler;
+ showPanningState();
+ }
+
+ ConnectionBean getConnection() {
+ return connection;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onCreateDialog(int)
+ */
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case R.layout.entertext:
+ return new EnterTextDialog(this);
+ }
+ // Default to meta key dialog
+ return new MetaKeyDialog(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog)
+ */
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ super.onPrepareDialog(id, dialog);
+ if (dialog instanceof ConnectionSettable)
+ ((ConnectionSettable) dialog).setConnection(connection);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // ignore orientation/keyboard change
+ super.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ vncCanvas.disableRepaints();
+ super.onStop();
+ }
+
+ @Override
+ protected void onRestart() {
+ vncCanvas.enableRepaints();
+ super.onRestart();
+ }
+
+ /** {@inheritDoc} */
+/*
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.vnccanvasactivitymenu, menu);
+
+ if (vncCanvas.scaling != null)
+ menu.findItem(vncCanvas.scaling.getId()).setChecked(true);
+
+ Menu inputMenu = menu.findItem(R.id.itemInputMode).getSubMenu();
+
+ inputModeMenuItems = new MenuItem[inputModeIds.length];
+ for (int i = 0; i < inputModeIds.length; i++) {
+ inputModeMenuItems[i] = inputMenu.findItem(inputModeIds[i]);
+ }
+ updateInputMenu();
+ menu.findItem(R.id.itemFollowMouse).setChecked(
+ connection.getFollowMouse());
+ menu.findItem(R.id.itemFollowPan).setChecked(connection.getFollowPan());
+ return true;
+ }
+*/
+
+ /**
+ * Change the input mode sub-menu to reflect change in scaling
+ */
+ void updateInputMenu() {
+ if (inputModeMenuItems == null || vncCanvas.scaling == null) {
+ return;
+ }
+ for (MenuItem item : inputModeMenuItems) {
+ item.setEnabled(vncCanvas.scaling
+ .isValidInputMode(item.getItemId()));
+ if (getInputHandlerById(item.getItemId()) == inputHandler)
+ item.setChecked(true);
+ }
+ }
+
+ /**
+ * If id represents an input handler, return that; otherwise return null
+ *
+ * @param id
+ * @return
+ */
+ AbstractInputHandler getInputHandlerById(int id) {
+ if (inputModeHandlers == null) {
+ inputModeHandlers = new AbstractInputHandler[inputModeIds.length];
+ }
+ for (int i = 0; i < inputModeIds.length; ++i) {
+ if (inputModeIds[i] == id) {
+ if (inputModeHandlers[i] == null) {
+ switch (id) {
+ case R.id.itemInputFitToScreen:
+ inputModeHandlers[i] = new FitToScreenMode();
+ break;
+ case R.id.itemInputPan:
+ inputModeHandlers[i] = new PanMode();
+ break;
+ case R.id.itemInputMouse:
+ inputModeHandlers[i] = new MouseMode();
+ break;
+ case R.id.itemInputTouchPanTrackballMouse:
+ inputModeHandlers[i] = new TouchPanTrackballMouse();
+ break;
+ case R.id.itemInputDPadPanTouchMouse:
+ inputModeHandlers[i] = new DPadPanTouchMouseMode();
+ break;
+ case R.id.itemInputTouchPanZoomMouse:
+ inputModeHandlers[i] = new ZoomInputHandler();
+ break;
+ case R.id.itemInputTouchpad:
+ inputModeHandlers[i] = new TouchpadInputHandler();
+ break;
+ }
+ }
+ return inputModeHandlers[i];
+ }
+ }
+ return null;
+ }
+
+ AbstractInputHandler getInputHandlerByName(String name) {
+ AbstractInputHandler result = null;
+ for (int id : inputModeIds) {
+ AbstractInputHandler handler = getInputHandlerById(id);
+ if (handler.getName().equals(name)) {
+ result = handler;
+ break;
+ }
+ }
+ if (result == null) {
+ result = getInputHandlerById(R.id.itemInputTouchPanZoomMouse);
+ }
+ return result;
+ }
+
+ int getModeIdFromHandler(AbstractInputHandler handler) {
+ for (int id : inputModeIds) {
+ if (handler == getInputHandlerById(id))
+ return id;
+ }
+ return R.id.itemInputTouchPanZoomMouse;
+ }
+
+ private MetaKeyBean lastSentKey;
+
+ private void sendSpecialKeyAgain() {
+ if (lastSentKey == null
+ || lastSentKey.get_Id() != connection.getLastMetaKeyId()) {
+ ArrayList keys = new ArrayList();
+ Cursor c = database.getReadableDatabase().rawQuery(
+ MessageFormat.format("SELECT * FROM {0} WHERE {1} = {2}",
+ MetaKeyBean.GEN_TABLE_NAME,
+ MetaKeyBean.GEN_FIELD__ID, connection
+ .getLastMetaKeyId()),
+ MetaKeyDialog.EMPTY_ARGS);
+ MetaKeyBean.Gen_populateFromCursor(c, keys, MetaKeyBean.NEW);
+ c.close();
+ if (keys.size() > 0) {
+ lastSentKey = keys.get(0);
+ } else {
+ lastSentKey = null;
+ }
+ }
+ if (lastSentKey != null)
+ vncCanvas.sendMetaKey(lastSentKey);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (isFinishing()) {
+ vncCanvas.closeConnection();
+ vncCanvas.onDestroy();
+ database.close();
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_MENU)
+ return super.onKeyDown(keyCode, evt);
+
+ return inputHandler.onKeyDown(keyCode, evt);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_MENU)
+ return super.onKeyUp(keyCode, evt);
+
+ return inputHandler.onKeyUp(keyCode, evt);
+ }
+
+ public void showPanningState() {
+ Toast.makeText(this, inputHandler.getHandlerDescription(),
+ Toast.LENGTH_SHORT).show();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ trackballButtonDown = true;
+ break;
+ case MotionEvent.ACTION_UP:
+ trackballButtonDown = false;
+ break;
+ }
+ return inputHandler.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return inputHandler.onTouchEvent(event);
+ }
+
+ private void selectColorModel() {
+ // Stop repainting the desktop
+ // because the display is composited!
+ vncCanvas.disableRepaints();
+
+ String[] choices = new String[COLORMODEL.values().length];
+ int currentSelection = -1;
+ for (int i = 0; i < choices.length; i++) {
+ COLORMODEL cm = COLORMODEL.values()[i];
+ choices[i] = cm.toString();
+ if (vncCanvas.isColorModel(cm))
+ currentSelection = i;
+ }
+
+ final Dialog dialog = new Dialog(this);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ ListView list = new ListView(this);
+ list.setAdapter(new ArrayAdapter(this,
+ android.R.layout.simple_list_item_checked, choices));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setItemChecked(currentSelection, true);
+ list.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView> arg0, View arg1, int arg2,
+ long arg3) {
+ dialog.dismiss();
+ COLORMODEL cm = COLORMODEL.values()[arg2];
+ vncCanvas.setColorModel(cm);
+ connection.setColorModel(cm.nameString());
+ connection.save(database.getWritableDatabase());
+ Toast.makeText(VncCanvasActivity.this,
+ "Updating Color Model to " + cm.toString(),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ dialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface arg0) {
+ Log.i(TAG, "Color Model Selector dismissed");
+ // Restore desktop repaints
+ vncCanvas.enableRepaints();
+ }
+ });
+ dialog.setContentView(list);
+ dialog.show();
+ }
+
+ float panTouchX, panTouchY;
+
+ /**
+ * Pan based on touch motions
+ *
+ * @param event
+ */
+ private boolean pan(MotionEvent event) {
+ float curX = event.getX();
+ float curY = event.getY();
+ int dX = (int) (panTouchX - curX);
+ int dY = (int) (panTouchY - curY);
+
+ return vncCanvas.pan(dX, dY);
+ }
+
+ boolean defaultKeyDownHandler(int keyCode, KeyEvent evt) {
+ if (vncCanvas.processLocalKeyEvent(keyCode, evt))
+ return true;
+ return super.onKeyDown(keyCode, evt);
+ }
+
+ boolean defaultKeyUpHandler(int keyCode, KeyEvent evt) {
+ if (vncCanvas.processLocalKeyEvent(keyCode, evt))
+ return true;
+ return super.onKeyUp(keyCode, evt);
+ }
+
+ boolean touchPan(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ panTouchX = event.getX();
+ panTouchY = event.getY();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ pan(event);
+ panTouchX = event.getX();
+ panTouchY = event.getY();
+ break;
+ case MotionEvent.ACTION_UP:
+ pan(event);
+ break;
+ }
+ return true;
+ }
+
+ private static int convertTrackballDelta(double delta) {
+ return (int) Math.pow(Math.abs(delta) * 6.01, 2.5)
+ * (delta < 0.0 ? -1 : 1);
+ }
+
+ boolean trackballMouse(MotionEvent evt) {
+ int dx = convertTrackballDelta(evt.getX());
+ int dy = convertTrackballDelta(evt.getY());
+
+ evt.offsetLocation(vncCanvas.mouseX + dx - evt.getX(), vncCanvas.mouseY
+ + dy - evt.getY());
+
+ if (vncCanvas.processPointerEvent(evt, trackballButtonDown)) {
+ return true;
+ }
+ return VncCanvasActivity.super.onTouchEvent(evt);
+ }
+
+ long hideZoomAfterMs;
+ static final long ZOOM_HIDE_DELAY_MS = 2500;
+ HideZoomRunnable hideZoomInstance = new HideZoomRunnable();
+
+ private void showZoomer(boolean force) {
+ if (force || zoomer.getVisibility() != View.VISIBLE) {
+ zoomer.show();
+ hideZoomAfterMs = SystemClock.uptimeMillis() + ZOOM_HIDE_DELAY_MS;
+ vncCanvas.handler
+ .postAtTime(hideZoomInstance, hideZoomAfterMs + 10);
+ }
+ }
+
+ private class HideZoomRunnable implements Runnable {
+ public void run() {
+ if (SystemClock.uptimeMillis() >= hideZoomAfterMs) {
+ zoomer.hide();
+ }
+ }
+
+ }
+
+ /**
+ * Touches and dpad (trackball) pan the screen
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ class PanMode implements AbstractInputHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ // DPAD KeyDown events are move MotionEvents in Panning Mode
+ final int dPos = 100;
+ boolean result = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ onTouchEvent(MotionEvent
+ .obtain(1, System.currentTimeMillis(),
+ MotionEvent.ACTION_MOVE, panTouchX + dPos,
+ panTouchY, 0));
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ onTouchEvent(MotionEvent
+ .obtain(1, System.currentTimeMillis(),
+ MotionEvent.ACTION_MOVE, panTouchX - dPos,
+ panTouchY, 0));
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ onTouchEvent(MotionEvent
+ .obtain(1, System.currentTimeMillis(),
+ MotionEvent.ACTION_MOVE, panTouchX, panTouchY
+ + dPos, 0));
+ result = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ onTouchEvent(MotionEvent
+ .obtain(1, System.currentTimeMillis(),
+ MotionEvent.ACTION_MOVE, panTouchX, panTouchY
+ - dPos, 0));
+ result = true;
+ break;
+ default:
+ result = defaultKeyDownHandler(keyCode, evt);
+ break;
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ // Ignore KeyUp events for DPAD keys in Panning Mode; trackball
+ // button switches to mouse mode
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ inputHandler = getInputHandlerById(R.id.itemInputMouse);
+ connection.setInputMode(inputHandler.getName());
+ connection.save(database.getWritableDatabase());
+ updateInputMenu();
+ showPanningState();
+ return true;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return true;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return true;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ return true;
+ }
+ return defaultKeyUpHandler(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return touchPan(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_panning);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "PAN_MODE";
+ }
+
+ }
+
+ /**
+ * The touchscreen pans the screen; the trackball moves and clicks the
+ * mouse.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ public class TouchPanTrackballMouse implements AbstractInputHandler {
+ private DPadMouseKeyHandler keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this, vncCanvas.handler);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ return touchPan(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(
+ R.string.input_mode_touchpad_pan_trackball_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "TOUCH_PAN_TRACKBALL_MOUSE";
+ }
+
+ }
+
+ static final String FIT_SCREEN_NAME = "FIT_SCREEN";
+ /** Internal name for default input mode with Zoom scaling */
+ static final String TOUCH_ZOOM_MODE = "TOUCH_ZOOM_MODE";
+
+ static final String TOUCHPAD_MODE = "TOUCHPAD_MODE";
+
+ /**
+ * In fit-to-screen mode, no panning. Trackball and touchscreen work as
+ * mouse.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ public class FitToScreenMode implements AbstractInputHandler {
+ private DPadMouseKeyHandler keyHandler = new DPadMouseKeyHandler(VncCanvasActivity.this, vncCanvas.handler);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyDown(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ return keyHandler.onKeyUp(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return trackballMouse(evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_fit_to_screen);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return FIT_SCREEN_NAME;
+ }
+
+ }
+
+ /**
+ * Touch screen controls, clicks the mouse.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ class MouseMode implements AbstractInputHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
+ return true;
+ return defaultKeyDownHandler(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ inputHandler = getInputHandlerById(R.id.itemInputPan);
+ showPanningState();
+ connection.setInputMode(inputHandler.getName());
+ connection.save(database.getWritableDatabase());
+ updateInputMenu();
+ return true;
+ }
+ return defaultKeyUpHandler(keyCode, evt);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Mouse Pointer Control Mode
+ // Pointer event is absolute coordinates.
+
+ vncCanvas.changeTouchCoordinatesToFullFrame(event);
+ if (vncCanvas.processPointerEvent(event, true))
+ return true;
+ return VncCanvasActivity.super.onTouchEvent(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(R.string.input_mode_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "MOUSE";
+ }
+
+ }
+
+ /**
+ * Touch screen controls, clicks the mouse. DPad pans the screen
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+ class DPadPanTouchMouseMode implements AbstractInputHandler {
+
+ private boolean isPanning;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyDown(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ int xv = 0;
+ int yv = 0;
+ boolean result = true;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ xv = -1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ xv = 1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ yv = -1;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ yv = 1;
+ break;
+ default:
+ result = defaultKeyDownHandler(keyCode, evt);
+ break;
+ }
+ if ((xv != 0 || yv != 0) && !isPanning) {
+ final int x = xv;
+ final int y = yv;
+ isPanning = true;
+ panner.start(x, y, new Panner.VelocityUpdater() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.Panner.VelocityUpdater#updateVelocity(android.graphics.Point,
+ * long)
+ */
+ @Override
+ public boolean updateVelocity(PointF p, long interval) {
+ double scale = (2.0 * (double) interval / 50.0);
+ if (Math.abs(p.x) < 500)
+ p.x += (int) (scale * x);
+ if (Math.abs(p.y) < 500)
+ p.y += (int) (scale * y);
+ return true;
+ }
+
+ });
+ vncCanvas.pan(x, y);
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onKeyUp(int,
+ * android.view.KeyEvent)
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent evt) {
+ boolean result = false;
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ panner.stop();
+ isPanning = false;
+ result = true;
+ break;
+ default:
+ result = defaultKeyUpHandler(keyCode, evt);
+ break;
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Mouse Pointer Control Mode
+ // Pointer event is absolute coordinates.
+
+ vncCanvas.changeTouchCoordinatesToFullFrame(event);
+ if (vncCanvas.processPointerEvent(event, true))
+ return true;
+ return VncCanvasActivity.super.onTouchEvent(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#onTrackballEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTrackballEvent(MotionEvent evt) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#handlerDescription()
+ */
+ @Override
+ public CharSequence getHandlerDescription() {
+ return getResources().getText(
+ R.string.input_mode_dpad_pan_touchpad_mouse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.androidVNC.AbstractInputHandler#getName()
+ */
+ @Override
+ public String getName() {
+ return "DPAD_PAN_TOUCH_MOUSE";
+ }
+
+ }
+}
diff --git a/app/src/main/java/android/androidVNC/VncConstants.java b/app/src/main/java/android/androidVNC/VncConstants.java
new file mode 100644
index 000000000..7aaa22bfd
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncConstants.java
@@ -0,0 +1,8 @@
+package android.androidVNC;
+
+/**
+ * Keys for intent values
+ */
+public class VncConstants {
+ public static final String CONNECTION = "android.androidVNC.CONNECTION";
+}
diff --git a/app/src/main/java/android/androidVNC/VncDatabase.java b/app/src/main/java/android/androidVNC/VncDatabase.java
new file mode 100644
index 000000000..84e548b98
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/VncDatabase.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class VncDatabase extends SQLiteOpenHelper {
+ static final int DBV_0_2_X = 9;
+ static final int DBV_0_2_4 = 10;
+ static final int DBV_0_4_7 = 11;
+ static final int DBV_0_5_0 = 12;
+
+ public final static String TAG = VncDatabase.class.toString();
+
+ VncDatabase(Context context)
+ {
+ super(context,"VncDatabase",null,DBV_0_5_0);
+ }
+
+ /* (non-Javadoc)
+ * @see android.database.sqlite.SQLiteOpenHelper#onCreate(android.database.sqlite.SQLiteDatabase)
+ */
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(AbstractConnectionBean.GEN_CREATE);
+ db.execSQL(MostRecentBean.GEN_CREATE);
+ db.execSQL(MetaList.GEN_CREATE);
+ db.execSQL(AbstractMetaKeyBean.GEN_CREATE);
+ db.execSQL(SentTextBean.GEN_CREATE);
+
+ db.execSQL("INSERT INTO "+MetaList.GEN_TABLE_NAME+" VALUES ( 1, 'DEFAULT')");
+ }
+
+ private void defaultUpgrade(SQLiteDatabase db)
+ {
+ Log.i(TAG, "Doing default database upgrade (drop and create tables)");
+ db.execSQL("DROP TABLE IF EXISTS " + AbstractConnectionBean.GEN_TABLE_NAME);
+ db.execSQL("DROP TABLE IF EXISTS " + MostRecentBean.GEN_TABLE_NAME);
+ db.execSQL("DROP TABLE IF EXISTS " + MetaList.GEN_TABLE_NAME);
+ db.execSQL("DROP TABLE IF EXISTS " + AbstractMetaKeyBean.GEN_TABLE_NAME);
+ db.execSQL("DROP TABLE IF EXISTS " + SentTextBean.GEN_TABLE_NAME);
+ onCreate(db);
+ }
+
+ /* (non-Javadoc)
+ * @see android.database.sqlite.SQLiteOpenHelper#onUpgrade(android.database.sqlite.SQLiteDatabase, int, int)
+ */
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < DBV_0_2_X)
+ {
+ defaultUpgrade(db);
+ }
+ else {
+ if (oldVersion == DBV_0_2_X)
+ {
+ Log.i(TAG, "Doing upgrade from 9 to 10");
+ db.execSQL("ALTER TABLE " + AbstractConnectionBean.GEN_TABLE_NAME + " RENAME TO OLD_" +
+ AbstractConnectionBean.GEN_TABLE_NAME);
+ db.execSQL(AbstractConnectionBean.GEN_CREATE);
+ db.execSQL("INSERT INTO " + AbstractConnectionBean.GEN_TABLE_NAME +
+ " SELECT *, 0 FROM OLD_" + AbstractConnectionBean.GEN_TABLE_NAME);
+ db.execSQL("DROP TABLE OLD_" + AbstractConnectionBean.GEN_TABLE_NAME);
+ oldVersion = DBV_0_2_4;
+ }
+ if (oldVersion == DBV_0_2_4)
+ {
+ Log.i(TAG,"Doing upgrade from 10 to 11");
+ db.execSQL("ALTER TABLE " + AbstractConnectionBean.GEN_TABLE_NAME + " ADD COLUMN " +AbstractConnectionBean.GEN_FIELD_USERNAME+" TEXT");
+ db.execSQL("ALTER TABLE " + AbstractConnectionBean.GEN_TABLE_NAME + " ADD COLUMN " +AbstractConnectionBean.GEN_FIELD_SECURECONNECTIONTYPE+" TEXT");
+ db.execSQL("ALTER TABLE " + MostRecentBean.GEN_TABLE_NAME + " ADD COLUMN " + MostRecentBean.GEN_FIELD_SHOW_SPLASH_VERSION + " INTEGER");
+ db.execSQL("ALTER TABLE " + MostRecentBean.GEN_TABLE_NAME + " ADD COLUMN " + MostRecentBean.GEN_FIELD_TEXT_INDEX);
+ oldVersion = DBV_0_4_7;
+ }
+ Log.i(TAG,"Doing upgrade from 11 to 12");
+ // Haven't been using SentText before, primary key handling changed so drop and recreate it
+ db.execSQL("DROP TABLE IF EXISTS " + SentTextBean.GEN_TABLE_NAME);
+ db.execSQL(SentTextBean.GEN_CREATE);
+ db.execSQL("ALTER TABLE " + AbstractConnectionBean.GEN_TABLE_NAME + " ADD COLUMN " +AbstractConnectionBean.GEN_FIELD_SHOWZOOMBUTTONS+" INTEGER DEFAULT 1");
+ db.execSQL("ALTER TABLE " + AbstractConnectionBean.GEN_TABLE_NAME + " ADD COLUMN " +AbstractConnectionBean.GEN_FIELD_DOUBLE_TAP_ACTION+" TEXT");
+ }
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/ZlibInStream.java b/app/src/main/java/android/androidVNC/ZlibInStream.java
new file mode 100644
index 000000000..91103a184
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ZlibInStream.java
@@ -0,0 +1,112 @@
+package android.androidVNC;
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// A ZlibInStream reads from a zlib.io.InputStream
+//
+
+public class ZlibInStream extends InStream {
+
+ static final int defaultBufSize = 16384;
+
+ public ZlibInStream(int bufSize_) {
+ bufSize = bufSize_;
+ b = new byte[bufSize];
+ ptr = end = ptrOffset = 0;
+ inflater = new java.util.zip.Inflater();
+ }
+
+ public ZlibInStream() { this(defaultBufSize); }
+
+ public void setUnderlying(InStream is, int bytesIn_) {
+ underlying = is;
+ bytesIn = bytesIn_;
+ ptr = end = 0;
+ }
+
+ public void reset() throws Exception {
+ ptr = end = 0;
+ if (underlying == null) return;
+
+ while (bytesIn > 0) {
+ decompress();
+ end = 0; // throw away any data
+ }
+ underlying = null;
+ }
+
+ public int pos() { return ptrOffset + ptr; }
+
+ protected int overrun(int itemSize, int nItems) throws Exception {
+ if (itemSize > bufSize)
+ throw new Exception("ZlibInStream overrun: max itemSize exceeded");
+ if (underlying == null)
+ throw new Exception("ZlibInStream overrun: no underlying stream");
+
+ if (end - ptr != 0)
+ System.arraycopy(b, ptr, b, 0, end - ptr);
+
+ ptrOffset += ptr;
+ end -= ptr;
+ ptr = 0;
+
+ while (end < itemSize) {
+ decompress();
+ }
+
+ if (itemSize * nItems > end)
+ nItems = end / itemSize;
+
+ return nItems;
+ }
+
+ // decompress() calls the decompressor once. Note that this won't
+ // necessarily generate any output data - it may just consume some input
+ // data. Returns false if wait is false and we would block on the underlying
+ // stream.
+
+ private void decompress() throws Exception {
+ try {
+ underlying.check(1);
+ int avail_in = underlying.getend() - underlying.getptr();
+ if (avail_in > bytesIn)
+ avail_in = bytesIn;
+
+ if (inflater.needsInput()) {
+ inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in);
+ }
+
+ int n = inflater.inflate(b, end, bufSize - end);
+
+ end += n;
+ if (inflater.needsInput()) {
+ bytesIn -= avail_in;
+ underlying.setptr(underlying.getptr() + avail_in);
+ }
+ } catch (java.util.zip.DataFormatException e) {
+ throw new Exception("ZlibInStream: inflate failed");
+ }
+ }
+
+ private InStream underlying;
+ private int bufSize;
+ private int ptrOffset;
+ private java.util.zip.Inflater inflater;
+ private int bytesIn;
+}
diff --git a/app/src/main/java/android/androidVNC/ZoomScaling.java b/app/src/main/java/android/androidVNC/ZoomScaling.java
new file mode 100644
index 000000000..b7f249091
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/ZoomScaling.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package android.androidVNC;
+
+import android.graphics.*;
+import android.widget.ImageView.*;
+import net.kdt.pojavlaunch.*;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class ZoomScaling extends AbstractScaling {
+
+ static final String TAG = "ZoomScaling";
+
+ private Matrix matrix;
+ int canvasXOffset;
+ int canvasYOffset;
+ float scaling;
+ float minimumScale;
+
+ /**
+ * @param id
+ * @param scaleType
+ */
+ public ZoomScaling() {
+ super(R.id.itemZoomable, ScaleType.MATRIX);
+ matrix = new Matrix();
+ scaling = 1;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getDefaultHandlerId()
+ */
+ @Override
+ int getDefaultHandlerId() {
+ return R.id.itemInputTouchPanZoomMouse;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isAbleToPan()
+ */
+ @Override
+ boolean isAbleToPan() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#isValidInputMode(int)
+ */
+ @Override
+ boolean isValidInputMode(int mode) {
+ return mode != R.id.itemInputFitToScreen;
+ }
+
+ /**
+ * Call after scaling and matrix have been changed to resolve scrolling
+ * @param activity
+ */
+ private void resolveZoom(VncCanvasActivity activity)
+ {
+ activity.vncCanvas.scrollToAbsolute();
+ activity.vncCanvas.pan(0,0);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#zoomIn(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ void zoomIn(VncCanvasActivity activity) {
+ resetMatrix();
+ standardizeScaling();
+ scaling += 0.25;
+ if (scaling > 4.0)
+ {
+ scaling = (float)4.0;
+ activity.zoomer.setIsZoomInEnabled(false);
+ }
+ activity.zoomer.setIsZoomOutEnabled(true);
+ matrix.postScale(scaling, scaling);
+ //Log.v(TAG,String.format("before set matrix scrollx = %d scrolly = %d", activity.vncCanvas.getScrollX(), activity.vncCanvas.getScrollY()));
+ activity.vncCanvas.setImageMatrix(matrix);
+ resolveZoom(activity);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#getScale()
+ */
+ @Override
+ float getScale() {
+ return scaling;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#zoomOut(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ void zoomOut(VncCanvasActivity activity) {
+ resetMatrix();
+ standardizeScaling();
+ scaling -= 0.25;
+ if (scaling < minimumScale)
+ {
+ scaling = minimumScale;
+ activity.zoomer.setIsZoomOutEnabled(false);
+ }
+ activity.zoomer.setIsZoomInEnabled(true);
+ matrix.postScale(scaling, scaling);
+ //Log.v(TAG,String.format("before set matrix scrollx = %d scrolly = %d", activity.vncCanvas.getScrollX(), activity.vncCanvas.getScrollY()));
+ activity.vncCanvas.setImageMatrix(matrix);
+ //Log.v(TAG,String.format("after set matrix scrollx = %d scrolly = %d", activity.vncCanvas.getScrollX(), activity.vncCanvas.getScrollY()));
+ resolveZoom(activity);
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#adjust(android.androidVNC.VncCanvasActivity, float, float, float)
+ */
+ @Override
+ void adjust(VncCanvasActivity activity, float scaleFactor, float fx,
+ float fy) {
+ float newScale = scaleFactor * scaling;
+ if (scaleFactor < 1)
+ {
+ if (newScale < minimumScale)
+ {
+ newScale = minimumScale;
+ activity.zoomer.setIsZoomOutEnabled(false);
+ }
+ activity.zoomer.setIsZoomInEnabled(true);
+ }
+ else
+ {
+ if (newScale > 4)
+ {
+ newScale = 4;
+ activity.zoomer.setIsZoomInEnabled(false);
+ }
+ activity.zoomer.setIsZoomOutEnabled(true);
+ }
+ // ax is the absolute x of the focus
+ int xPan = activity.vncCanvas.absoluteXPosition;
+ float ax = (fx / scaling) + xPan;
+ float newXPan = (scaling * xPan - scaling * ax + newScale * ax)/newScale;
+ int yPan = activity.vncCanvas.absoluteYPosition;
+ float ay = (fy / scaling) + yPan;
+ float newYPan = (scaling * yPan - scaling * ay + newScale * ay)/newScale;
+ resetMatrix();
+ scaling = newScale;
+ matrix.postScale(scaling, scaling);
+ activity.vncCanvas.setImageMatrix(matrix);
+ resolveZoom(activity);
+ activity.vncCanvas.pan((int)(newXPan - xPan), (int)(newYPan - yPan));
+ }
+
+ private void resetMatrix()
+ {
+ matrix.reset();
+ matrix.preTranslate(canvasXOffset, canvasYOffset);
+ }
+
+ /**
+ * Set scaling to one of the clicks on the zoom scale
+ */
+ private void standardizeScaling()
+ {
+ scaling = ((float)((int)(scaling * 4))) / 4;
+ }
+
+ /* (non-Javadoc)
+ * @see android.androidVNC.AbstractScaling#setScaleTypeForActivity(android.androidVNC.VncCanvasActivity)
+ */
+ @Override
+ void setScaleTypeForActivity(VncCanvasActivity activity) {
+ super.setScaleTypeForActivity(activity);
+ scaling = (float)1.0;
+ minimumScale = activity.vncCanvas.bitmapData.getMinimumScale();
+ canvasXOffset = -activity.vncCanvas.getCenteredXOffset();
+ canvasYOffset = -activity.vncCanvas.getCenteredYOffset();
+ resetMatrix();
+ activity.vncCanvas.setImageMatrix(matrix);
+ // Reset the pan position to (0,0)
+ resolveZoom(activity);
+ }
+
+}
diff --git a/app/src/main/java/android/androidVNC/androidVNC.java b/app/src/main/java/android/androidVNC/androidVNC.java
new file mode 100644
index 000000000..4500d8c3c
--- /dev/null
+++ b/app/src/main/java/android/androidVNC/androidVNC.java
@@ -0,0 +1,379 @@
+/*
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// androidVNC is the Activity for setting VNC server IP and port.
+//
+
+package android.androidVNC;
+
+import android.app.*;
+import android.app.ActivityManager.*;
+import android.content.*;
+import android.database.sqlite.*;
+import android.os.*;
+import android.support.v7.app.*;
+import android.view.*;
+import android.widget.*;
+import java.util.*;
+import net.kdt.pojavlaunch.*;
+
+public class androidVNC extends AppCompatActivity {
+ private EditText ipText;
+ private EditText portText;
+ private EditText passwordText;
+ private Button goButton;
+ private TextView repeaterText;
+ private RadioGroup groupForceFullScreen;
+ private Spinner colorSpinner;
+ private Spinner spinnerConnection;
+ private VncDatabase database;
+ private ConnectionBean selected;
+ private EditText textNickname;
+ private EditText textUsername;
+ private CheckBox checkboxKeepPassword;
+ private CheckBox checkboxLocalCursor;
+ private boolean repeaterTextSet;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+
+ super.onCreate(icicle);
+ setContentView(R.layout.androidvncmain);
+
+ ipText = (EditText) findViewById(R.id.textIP);
+ portText = (EditText) findViewById(R.id.textPORT);
+ passwordText = (EditText) findViewById(R.id.textPASSWORD);
+ textNickname = (EditText) findViewById(R.id.textNickname);
+ textUsername = (EditText) findViewById(R.id.textUsername);
+ goButton = (Button) findViewById(R.id.buttonGO);
+ ((Button) findViewById(R.id.buttonRepeater)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showDialog(R.layout.repeater_dialog);
+ }
+ });
+ ((Button)findViewById(R.id.buttonImportExport)).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showDialog(R.layout.importexport);
+ }
+ });
+ colorSpinner = (Spinner)findViewById(R.id.colorformat);
+ COLORMODEL[] models=COLORMODEL.values();
+ ArrayAdapter colorSpinnerAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, models);
+ groupForceFullScreen = (RadioGroup)findViewById(R.id.groupForceFullScreen);
+ checkboxKeepPassword = (CheckBox)findViewById(R.id.checkboxKeepPassword);
+ checkboxLocalCursor = (CheckBox)findViewById(R.id.checkboxUseLocalCursor);
+ colorSpinner.setAdapter(colorSpinnerAdapter);
+ colorSpinner.setSelection(0);
+ spinnerConnection = (Spinner)findViewById(R.id.spinnerConnection);
+ spinnerConnection.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> ad, View view, int itemIndex, long id) {
+ selected = (ConnectionBean)ad.getSelectedItem();
+ updateViewFromSelected();
+ }
+ @Override
+ public void onNothingSelected(AdapterView> ad) {
+ selected = null;
+ }
+ });
+ spinnerConnection.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, int, long)
+ */
+ @Override
+ public boolean onItemLongClick(AdapterView> arg0, View arg1,
+ int arg2, long arg3) {
+ spinnerConnection.setSelection(arg2);
+ selected = (ConnectionBean)spinnerConnection.getItemAtPosition(arg2);
+ canvasStart();
+ return true;
+ }
+
+ });
+ repeaterText = (TextView)findViewById(R.id.textRepeaterId);
+ goButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ canvasStart();
+ }
+ });
+
+ database = new VncDatabase(this);
+ }
+
+ protected void onDestroy() {
+ database.close();
+ super.onDestroy();
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Activity#onCreateDialog(int)
+ */
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ if (id == R.layout.importexport)
+ return new ImportExportDialog(this);
+ else
+ return new RepeaterDialog(this);
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.androidvncmenu,menu);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Activity#onMenuOpened(int, android.view.Menu)
+ */
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ menu.findItem(R.id.itemDeleteConnection).setEnabled(selected!=null && ! selected.isNew());
+ menu.findItem(R.id.itemSaveAsCopy).setEnabled(selected!=null && ! selected.isNew());
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId())
+ {
+ case R.id.itemSaveAsCopy :
+ if (selected.getNickname().equals(textNickname.getText().toString()))
+ textNickname.setText("Copy of "+selected.getNickname());
+ updateSelectedFromView();
+ selected.set_Id(0);
+ saveAndWriteRecent();
+ arriveOnPage();
+ break;
+ case R.id.itemDeleteConnection :
+ Utils.showYesNoPrompt(this, "Delete?", "Delete " + selected.getNickname() + "?",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int i)
+ {
+ selected.Gen_delete(database.getWritableDatabase());
+ arriveOnPage();
+ }
+ }, null);
+ break;
+ case R.id.itemOpenDoc :
+ Utils.showDocumentation(this);
+ break;
+ }
+ return true;
+ }
+
+ private void updateViewFromSelected() {
+ if (selected==null)
+ return;
+ ipText.setText(selected.getAddress());
+ portText.setText(Integer.toString(selected.getPort()));
+ if (selected.getKeepPassword() || selected.getPassword().length()>0) {
+ passwordText.setText(selected.getPassword());
+ }
+ groupForceFullScreen.check(selected.getForceFull()==BitmapImplHint.AUTO ? R.id.radioForceFullScreenAuto : (selected.getForceFull() == BitmapImplHint.FULL ? R.id.radioForceFullScreenOn : R.id.radioForceFullScreenOff));
+ checkboxKeepPassword.setChecked(selected.getKeepPassword());
+ checkboxLocalCursor.setChecked(selected.getUseLocalCursor());
+ textNickname.setText(selected.getNickname());
+ textUsername.setText(selected.getUserName());
+ COLORMODEL cm = COLORMODEL.valueOf(selected.getColorModel());
+ COLORMODEL[] colors=COLORMODEL.values();
+ for (int i=0; i recents = new ArrayList(1);
+ MostRecentBean.getAll(db, MostRecentBean.GEN_TABLE_NAME, recents, MostRecentBean.GEN_NEW);
+ if (recents.size() == 0)
+ return null;
+ return recents.get(0);
+ }
+
+ void arriveOnPage() {
+ ArrayList connections=new ArrayList();
+ ConnectionBean.getAll(database.getReadableDatabase(), ConnectionBean.GEN_TABLE_NAME, connections, ConnectionBean.newInstance);
+ Collections.sort(connections);
+ connections.add(0, new ConnectionBean());
+ int connectionIndex=0;
+ if ( connections.size()>1)
+ {
+ MostRecentBean mostRecent = getMostRecent(database.getReadableDatabase());
+ if (mostRecent != null)
+ {
+ for ( int i=1; i(this,android.R.layout.simple_spinner_item,
+ connections.toArray(new ConnectionBean[connections.size()])));
+ spinnerConnection.setSelection(connectionIndex,false);
+ selected=connections.get(connectionIndex);
+ updateViewFromSelected();
+ IntroTextDialog.showIntroTextIfNecessary(this, database);
+ }
+
+ protected void onStop() {
+ super.onStop();
+ if ( selected == null ) {
+ return;
+ }
+ updateSelectedFromView();
+ selected.save(database.getWritableDatabase());
+ }
+
+ VncDatabase getDatabaseHelper()
+ {
+ return database;
+ }
+
+ private void canvasStart() {
+ if (selected == null) return;
+ MemoryInfo info = Utils.getMemoryInfo(this);
+ if (info.lowMemory) {
+ // Low Memory situation. Prompt.
+ Utils.showYesNoPrompt(this, "Continue?", "Android reports low system memory.\nContinue with VNC connection?", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ vnc();
+ }
+ }, null);
+ } else
+ vnc();
+ }
+
+ private void saveAndWriteRecent()
+ {
+ SQLiteDatabase db = database.getWritableDatabase();
+ db.beginTransaction();
+ try
+ {
+ selected.save(db);
+ MostRecentBean mostRecent = getMostRecent(db);
+ if (mostRecent == null)
+ {
+ mostRecent = new MostRecentBean();
+ mostRecent.setConnectionId(selected.get_Id());
+ mostRecent.Gen_insert(db);
+ }
+ else
+ {
+ mostRecent.setConnectionId(selected.get_Id());
+ mostRecent.Gen_update(db);
+ }
+ db.setTransactionSuccessful();
+ }
+ finally
+ {
+ db.endTransaction();
+ }
+ }
+
+ private void vnc() {
+ updateSelectedFromView();
+ saveAndWriteRecent();
+ Intent intent = new Intent(this, VncCanvasActivity.class);
+ intent.putExtra(VncConstants.CONNECTION,selected.Gen_getValues());
+ startActivity(intent);
+ }
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerDefault.java b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerDefault.java
new file mode 100644
index 000000000..a5d9b603e
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerDefault.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.app.ActivityManager;
+
+/**
+ * @author Michael A. MacDonald
+ */
+class BCActivityManagerDefault implements IBCActivityManager {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCActivityManager#getMemoryClass(android.app.ActivityManager)
+ */
+ @Override
+ public int getMemoryClass(ActivityManager am) {
+ return 16;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerV5.java b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerV5.java
new file mode 100644
index 000000000..a764c76a9
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCActivityManagerV5.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.app.ActivityManager;
+
+/**
+ * @author Michael A. MacDonald
+ */
+public class BCActivityManagerV5 implements IBCActivityManager {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCActivityManager#getMemoryClass(android.app.ActivityManager)
+ */
+ @Override
+ public int getMemoryClass(ActivityManager am) {
+ return am.getMemoryClass();
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCFactory.java b/app/src/main/java/com/antlersoft/android/bc/BCFactory.java
new file mode 100644
index 000000000..29254c10d
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCFactory.java
@@ -0,0 +1,255 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.content.Context;
+
+/**
+ * Create interface implementations appropriate to the current version of the SDK;
+ * implementations can allow use of higher-level SDK calls in .apk's that will still run
+ * on lower-level SDK's
+ * @author Michael A. MacDonald
+ */
+public class BCFactory {
+
+ private static BCFactory _theInstance = new BCFactory();
+
+ private IBCActivityManager bcActivityManager;
+ private IBCGestureDetector bcGestureDetector;
+ private IBCHaptic bcHaptic;
+ private IBCMotionEvent bcMotionEvent;
+ private IBCStorageContext bcStorageContext;
+
+ /**
+ * This is here so checking the static doesn't get optimized away;
+ * note we can't use SDK_INT because that is too new
+ * @return sdk version
+ */
+ int getSdkVersion()
+ {
+ try
+ {
+ return Integer.parseInt(android.os.Build.VERSION.SDK);
+ }
+ catch (NumberFormatException nfe)
+ {
+ return 1;
+ }
+ }
+
+ /**
+ * Return the implementation of IBCActivityManager appropriate for this SDK level
+ * @return
+ */
+ public IBCActivityManager getBCActivityManager()
+ {
+ if (bcActivityManager == null)
+ {
+ synchronized (this)
+ {
+ if (bcActivityManager == null)
+ {
+ if (getSdkVersion() >= 5)
+ {
+ try
+ {
+ bcActivityManager = (IBCActivityManager)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCActivityManagerV5").newInstance();
+ }
+ catch (Exception ie)
+ {
+ bcActivityManager = new BCActivityManagerDefault();
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ else
+ {
+ bcActivityManager = new BCActivityManagerDefault();
+ }
+ }
+ }
+ }
+ return bcActivityManager;
+ }
+
+ /**
+ * Return the implementation of IBCGestureDetector appropriate for this SDK level
+ *
+ * Since we dropped support of SDK levels < 3, there is only one version at the moment.
+ * @return
+ */
+ public IBCGestureDetector getBCGestureDetector()
+ {
+ if (bcGestureDetector == null)
+ {
+ synchronized (this)
+ {
+ if (bcGestureDetector == null)
+ {
+ try
+ {
+ bcGestureDetector = (IBCGestureDetector)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCGestureDetectorDefault").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ return bcGestureDetector;
+ }
+
+ /**
+ * Return the implementation of IBCHaptic appropriate for this SDK level
+ *
+ * Since we dropped support of SDK levels prior to 3, there is only one version at the moment.
+ * @return
+ */
+ public IBCHaptic getBCHaptic()
+ {
+ if (bcHaptic == null)
+ {
+ synchronized (this)
+ {
+ if (bcHaptic == null)
+ {
+ try
+ {
+ bcHaptic = (IBCHaptic)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCHapticDefault").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ return bcHaptic;
+ }
+
+ /**
+ * Return the implementation of IBCMotionEvent appropriate for this SDK level
+ * @return
+ */
+ public IBCMotionEvent getBCMotionEvent()
+ {
+ if (bcMotionEvent == null)
+ {
+ synchronized (this)
+ {
+ if (bcMotionEvent == null)
+ {
+ if (getSdkVersion() >= 5)
+ {
+ try
+ {
+ bcMotionEvent = (IBCMotionEvent)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCMotionEvent5").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ else
+ {
+ try
+ {
+ bcMotionEvent = (IBCMotionEvent)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCMotionEvent4").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ }
+ return bcMotionEvent;
+ }
+
+ @SuppressWarnings("unchecked")
+ static private Class[] scaleDetectorConstructorArgs = new Class[] { Context.class, OnScaleGestureListener.class };
+
+ /**
+ * Return an instance of an implementation of {@link IBCScaleGestureDetector} appropriate to the SDK of this device.
+ * This will work very much like android.view.ScaleGestureDetector on SDK >= 5. For previous
+ * SDK versions, it is a dummy implementation that does nothing and will never call the listener.
+ *
+ * Note that unlike the other methods in this class, the returned interface instance is not
+ * stateless.
+ * @param context The context to which the detector is applied
+ * @param listener The listener to which the implementation will send scale events
+ * @return The gesture detector
+ */
+ public IBCScaleGestureDetector getScaleGestureDetector(Context context, OnScaleGestureListener listener)
+ {
+ IBCScaleGestureDetector result;
+
+ if (getSdkVersion() >= 5)
+ {
+ try {
+ result = (IBCScaleGestureDetector)getClass().getClassLoader().
+ loadClass("com.antlersoft.android.bc.ScaleGestureDetector").
+ getConstructor(scaleDetectorConstructorArgs).newInstance(new Object[] { context, listener });
+ } catch (Exception e) {
+ throw new RuntimeException("Error instantiating ScaleGestureDetector", e);
+ }
+ }
+ else
+ {
+ result = new DummyScaleGestureDetector();
+ }
+ return result;
+ }
+
+ /**
+ *
+ * @return An implementation of IBCStorageContext appropriate for the running Android release
+ */
+ public IBCStorageContext getStorageContext()
+ {
+ if (bcStorageContext == null)
+ {
+ synchronized (this)
+ {
+ if (bcStorageContext == null)
+ {
+ if (getSdkVersion() >= 8)
+ {
+ try
+ {
+ bcStorageContext = (IBCStorageContext)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCStorageContext8").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ else
+ {
+ try
+ {
+ bcStorageContext = (IBCStorageContext)getClass().getClassLoader().loadClass("com.antlersoft.android.bc.BCStorageContext7").newInstance();
+ }
+ catch (Exception ie)
+ {
+ throw new RuntimeException("Error instantiating", ie);
+ }
+ }
+ }
+ }
+ }
+ return bcStorageContext;
+ }
+
+ /**
+ * Returns the only instance of this class, which manages the SDK specific interface
+ * implementations
+ * @return Factory instance
+ */
+ public static BCFactory getInstance()
+ {
+ return _theInstance;
+ }
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCGestureDetectorDefault.java b/app/src/main/java/com/antlersoft/android/bc/BCGestureDetectorDefault.java
new file mode 100644
index 000000000..21e3ff7f4
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCGestureDetectorDefault.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnGestureListener;
+
+/**
+ * @author Michael A. MacDonald
+ */
+public class BCGestureDetectorDefault implements IBCGestureDetector {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCGestureDetector#createGestureDetector(android.content.Context, android.view.GestureDetector.OnGestureListener)
+ */
+ @Override
+ public GestureDetector createGestureDetector(Context context,
+ OnGestureListener listener) {
+ return new GestureDetector(context, listener);
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCHapticDefault.java b/app/src/main/java/com/antlersoft/android/bc/BCHapticDefault.java
new file mode 100644
index 000000000..c632c05ed
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCHapticDefault.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.View;
+import android.view.HapticFeedbackConstants;
+
+/**
+ * Implementation for SDK version >= 3
+ * @author Michael A. MacDonald
+ */
+class BCHapticDefault implements IBCHaptic {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCHaptic#performLongPressHaptic(android.view.View)
+ */
+ @Override
+ public boolean performLongPressHaptic(View v) {
+ return v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING|HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+ );
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCHaptic#setIsHapticEnabled(android.view.View, boolean)
+ */
+/*
+ * @Override
+ public boolean setIsHapticEnabled(View v, boolean enabled) {
+ return v.setHapticFeedbackEnabled(hapticFeedbackEnabled)
+ }
+*/
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent4.java b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent4.java
new file mode 100644
index 000000000..9e7eab2c8
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent4.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * Pre-sdk 5 version; add fake multi-touch sensing later?
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+class BCMotionEvent4 implements IBCMotionEvent {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCMotionEvent#getPointerCount(android.view.MotionEvent)
+ */
+ @Override
+ public int getPointerCount(MotionEvent evt) {
+ return 1;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent5.java b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent5.java
new file mode 100644
index 000000000..af15236d0
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCMotionEvent5.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class BCMotionEvent5 implements IBCMotionEvent {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCMotionEvent#getPointerCount(android.view.MotionEvent)
+ */
+ @Override
+ public int getPointerCount(MotionEvent evt) {
+ return evt.getPointerCount();
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCStorageContext7.java b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext7.java
new file mode 100644
index 000000000..27587830b
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext7.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2011 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import java.io.File;
+
+import android.content.Context;
+
+import android.os.Environment;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+public class BCStorageContext7 implements IBCStorageContext {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCStorageContext#getExternalStorageDir(android.content.Context, java.lang.String)
+ */
+ @Override
+ public File getExternalStorageDir(Context context, String type) {
+ File f = Environment.getExternalStorageDirectory();
+ f = new File(f, "Android/data/android.androidVNC/files");
+ if (type != null)
+ f=new File(f, type);
+ f.mkdirs();
+ return f;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/BCStorageContext8.java b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext8.java
new file mode 100644
index 000000000..3a7988fef
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/BCStorageContext8.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import java.io.File;
+
+import android.content.Context;
+
+/**
+ * @author Michael A. MacDonald
+ *
+ */
+class BCStorageContext8 implements IBCStorageContext {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCStorageContext#getExternalStorageDir(android.content.Context, java.lang.String)
+ */
+ @Override
+ public File getExternalStorageDir(Context context, String type) {
+ return context.getExternalFilesDir(type);
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/DummyScaleGestureDetector.java b/app/src/main/java/com/antlersoft/android/bc/DummyScaleGestureDetector.java
new file mode 100644
index 000000000..19dcc5331
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/DummyScaleGestureDetector.java
@@ -0,0 +1,119 @@
+/* Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * MODIFIED FOR ANTLERSOFT
+ *
+ * Changes for antlersoft/ vnc viewer for android
+ *
+ * Copyright (C) 2010 Michael A. MacDonald
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * Implementation of scale gesture detector interface for devices without multi-touch support; does nothing
+ * @author Michael A. MacDonald
+ *
+ */
+class DummyScaleGestureDetector implements IBCScaleGestureDetector {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getCurrentSpan()
+ */
+ @Override
+ public float getCurrentSpan() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getEventTime()
+ */
+ @Override
+ public long getEventTime() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getFocusX()
+ */
+ @Override
+ public float getFocusX() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getFocusY()
+ */
+ @Override
+ public float getFocusY() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getPreviousSpan()
+ */
+ @Override
+ public float getPreviousSpan() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getScaleFactor()
+ */
+ @Override
+ public float getScaleFactor() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getTimeDelta()
+ */
+ @Override
+ public long getTimeDelta() {
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#isInProgress()
+ */
+ @Override
+ public boolean isInProgress() {
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#onTouchEvent(android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return true;
+ }
+
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCActivityManager.java b/app/src/main/java/com/antlersoft/android/bc/IBCActivityManager.java
new file mode 100644
index 000000000..103c19ef1
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCActivityManager.java
@@ -0,0 +1,13 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.app.ActivityManager;
+
+/**
+ * @author Michael A. MacDonald
+ */
+public interface IBCActivityManager {
+ public int getMemoryClass(ActivityManager am);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCGestureDetector.java b/app/src/main/java/com/antlersoft/android/bc/IBCGestureDetector.java
new file mode 100644
index 000000000..04220a5bc
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCGestureDetector.java
@@ -0,0 +1,17 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.content.Context;
+import android.view.GestureDetector;
+
+/**
+ * Create a gesture detector in a version friendly way, avoiding incompatible API on older version
+ * and deprecated API on newer version
+ *
+ * @author Michael A. MacDonald
+ */
+public interface IBCGestureDetector {
+ public GestureDetector createGestureDetector(Context context, GestureDetector.OnGestureListener listener);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCHaptic.java b/app/src/main/java/com/antlersoft/android/bc/IBCHaptic.java
new file mode 100644
index 000000000..4a53f7c6a
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCHaptic.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.View;
+
+/**
+ * Access the Haptic interfaces added in version 3 without breaking compatibility
+ * @author Michael A. MacDonald
+ */
+public interface IBCHaptic {
+ public boolean performLongPressHaptic(View v);
+ /**
+ * Set whether haptic feedback is enabled on the view
+ * @param enabled
+ * @return Old value of setting
+ */
+ //public boolean setIsHapticEnabled(View v, boolean enabled);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCMotionEvent.java b/app/src/main/java/com/antlersoft/android/bc/IBCMotionEvent.java
new file mode 100644
index 000000000..166f23a60
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCMotionEvent.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * Access to SDK-dependent features of MotionEvent
+ *
+ * @see android.view.MotionEvent
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+public interface IBCMotionEvent {
+ /**
+ * Obtain the number of pointers active in the event
+ * @see android.view.MotionEvent#getPointerCount()
+ * @param evt
+ * @return number of pointers
+ */
+ int getPointerCount(MotionEvent evt);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCScaleGestureDetector.java b/app/src/main/java/com/antlersoft/android/bc/IBCScaleGestureDetector.java
new file mode 100644
index 000000000..d84fdfc70
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCScaleGestureDetector.java
@@ -0,0 +1,125 @@
+/* Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * MODIFIED FOR ANTLERSOFT
+ *
+ * Changes for antlersoft/ vnc viewer for android
+ *
+ * Copyright (C) 2010 Michael A. MacDonald
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+package com.antlersoft.android.bc;
+
+import android.view.MotionEvent;
+
+/**
+ * Backwards-compatibility interface to the android.view.ScaleGestureDetector introduced in Android SDK 8.
+ *
+ * This will be a working implementation of devices with SDK >= 5 (since I backported ScaleGestureDetector
+ * to 5) and a dummy implementation for older devices.
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+public interface IBCScaleGestureDetector {
+
+ public abstract boolean onTouchEvent(MotionEvent event);
+
+ /**
+ * Returns {@code true} if a two-finger scale gesture is in progress.
+ * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
+ */
+ public abstract boolean isInProgress();
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public abstract float getFocusX();
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public abstract float getFocusY();
+
+ /**
+ * Return the current distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public abstract float getCurrentSpan();
+
+ /**
+ * Return the previous distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public abstract float getPreviousSpan();
+
+ /**
+ * Return the scaling factor from the previous scale event to the current
+ * event. This value is defined as
+ * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public abstract float getScaleFactor();
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted scaling event and the current scaling event.
+ *
+ * @return Time difference since the last scaling event in milliseconds.
+ */
+ public abstract long getTimeDelta();
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public abstract long getEventTime();
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/antlersoft/android/bc/IBCStorageContext.java b/app/src/main/java/com/antlersoft/android/bc/IBCStorageContext.java
new file mode 100644
index 000000000..69d6ab615
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/IBCStorageContext.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010 Michael A. MacDonald
+ */
+package com.antlersoft.android.bc;
+
+import android.content.Context;
+
+import java.io.File;
+
+/**
+ * Provides a way to access the directory on external storage as returned by
+ * Context.getExternal... added in API 8 that will work with earlier API releases.
+ * @author Michael A. MacDonald
+ *
+ */
+public interface IBCStorageContext {
+ /**
+ *
+ * @param context Context within the application with which the storage will be associated
+ * @param type May be null; if specified, references a sub-directory within the base directory
+ * for the app in the external storage
+ * @return File representing abstract path of storage directory; refer to android.os.Environment to
+ * see if the path is actually accessible
+ */
+ public File getExternalStorageDir(Context context, String type);
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/OnScaleGestureListener.java b/app/src/main/java/com/antlersoft/android/bc/OnScaleGestureListener.java
new file mode 100644
index 000000000..699ae8d8f
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/OnScaleGestureListener.java
@@ -0,0 +1,94 @@
+/* Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * MODIFIED FOR ANTLERSOFT
+ *
+ * Changes for antlersoft/ vnc viewer for android
+ *
+ * Copyright (C) 2010 Michael A. MacDonald
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+package com.antlersoft.android.bc;
+
+
+/**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnScaleGestureListener}.
+ *
+ * An application will receive events in the following order:
+ *
+ *
One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
+ *
Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
+ *
One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
+ *
+ */
+public interface OnScaleGestureListener {
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ public boolean onScale(IBCScaleGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a scaling gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onScaleBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ public boolean onScaleBegin(IBCScaleGestureDetector detector);
+
+ /**
+ * Responds to the end of a scale gesture. Reported by existing
+ * pointers going up.
+ *
+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+ * and {@link ScaleGestureDetector#getFocusY()} will return the location
+ * of the pointer remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ public void onScaleEnd(IBCScaleGestureDetector detector);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/antlersoft/android/bc/ScaleGestureDetector.java b/app/src/main/java/com/antlersoft/android/bc/ScaleGestureDetector.java
new file mode 100644
index 000000000..cc2f5058a
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/ScaleGestureDetector.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * MODIFIED FOR ANTLERSOFT
+ *
+ * Changes for antlersoft/ vnc viewer for android
+ *
+ * Copyright (C) 2010 Michael A. MacDonald
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+*/
+
+package com.antlersoft.android.bc;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * Detects transformation gestures involving more than one pointer ("multitouch")
+ * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
+ * callback will notify users when a particular gesture event has occurred.
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ *
+ *
Create an instance of the {@code ScaleGestureDetector} for your
+ * {@link View}
+ *
In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ *
+ */
+class ScaleGestureDetector implements IBCScaleGestureDetector {
+ /**
+ * This value is the threshold ratio between our previous combined pressure
+ * and the current combined pressure. We will only fire an onScale event if
+ * the computed ratio between the current and previous event pressures is
+ * greater than this value. When pressure decreases rapidly between events
+ * the position values can often be imprecise, as it usually indicates
+ * that the user is in the process of lifting a pointer off of the device.
+ * Its value was tuned experimentally.
+ */
+ private static final float PRESSURE_THRESHOLD = 0.67f;
+
+ private final Context mContext;
+ private final OnScaleGestureListener mListener;
+ private boolean mGestureInProgress;
+
+ private MotionEvent mPrevEvent;
+ private MotionEvent mCurrEvent;
+
+ private float mFocusX;
+ private float mFocusY;
+ private float mPrevFingerDiffX;
+ private float mPrevFingerDiffY;
+ private float mCurrFingerDiffX;
+ private float mCurrFingerDiffY;
+ private float mCurrLen;
+ private float mPrevLen;
+ private float mScaleFactor;
+ private float mCurrPressure;
+ private float mPrevPressure;
+ private long mTimeDelta;
+
+ private final float mEdgeSlop;
+ private float mRightSlopEdge;
+ private float mBottomSlopEdge;
+ private boolean mSloppyGesture;
+
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+ ViewConfiguration config = ViewConfiguration.get(context);
+ mContext = context;
+ mListener = listener;
+ mEdgeSlop = config.getScaledEdgeSlop();
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#onTouchEvent(android.view.MotionEvent)
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ boolean handled = true;
+
+ if (!mGestureInProgress) {
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ // We have a new multi-finger gesture
+
+ // as orientation can change, query the metrics in touch down
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+ mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
+ // Be paranoid in case we missed an event
+ reset();
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ setContext(event);
+
+ // Check if we have a sloppy gesture. If so, delay
+ // the beginning of the gesture until we're sure that's
+ // what the user wanted. Sloppy gestures can happen if the
+ // edge of the user's hand is touching the screen, for example.
+ final float edgeSlop = mEdgeSlop;
+ final float rightSlop = mRightSlopEdge;
+ final float bottomSlop = mBottomSlopEdge;
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+ || x0 > rightSlop || y0 > bottomSlop;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+ || x1 > rightSlop || y1 > bottomSlop;
+
+ if (p0sloppy && p1sloppy) {
+ mFocusX = -1;
+ mFocusY = -1;
+ mSloppyGesture = true;
+ } else if (p0sloppy) {
+ mFocusX = event.getX(1);
+ mFocusY = event.getY(1);
+ mSloppyGesture = true;
+ } else if (p1sloppy) {
+ mFocusX = event.getX(0);
+ mFocusY = event.getY(0);
+ mSloppyGesture = true;
+ } else {
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mSloppyGesture) {
+ // Initiate sloppy gestures if we've moved outside of the slop area.
+ final float edgeSlop = mEdgeSlop;
+ final float rightSlop = mRightSlopEdge;
+ final float bottomSlop = mBottomSlopEdge;
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+ || x0 > rightSlop || y0 > bottomSlop;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+ || x1 > rightSlop || y1 > bottomSlop;
+
+ if(p0sloppy && p1sloppy) {
+ mFocusX = -1;
+ mFocusY = -1;
+ } else if (p0sloppy) {
+ mFocusX = event.getX(1);
+ mFocusY = event.getY(1);
+ } else if (p1sloppy) {
+ mFocusX = event.getX(0);
+ mFocusY = event.getY(0);
+ } else {
+ mSloppyGesture = false;
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mSloppyGesture) {
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+ >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
+ }
+ break;
+ }
+ } else {
+ // Transform gesture in progress - attempt to handle it
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended
+ setContext(event);
+
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+ >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
+
+ if (!mSloppyGesture) {
+ mListener.onScaleEnd(this);
+ }
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onScaleEnd(this);
+ }
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setContext(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit - this can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onScale(this);
+
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ /**
+ * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
+ */
+ private static float getRawX(MotionEvent event, int pointerIndex) {
+ float offset = event.getX() - event.getRawX();
+ return event.getX(pointerIndex) + offset;
+ }
+
+ /**
+ * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
+ */
+ private static float getRawY(MotionEvent event, int pointerIndex) {
+ float offset = event.getY() - event.getRawY();
+ return event.getY(pointerIndex) + offset;
+ }
+
+ private void setContext(MotionEvent curr) {
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+ mScaleFactor = -1;
+
+ final MotionEvent prev = mPrevEvent;
+
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+
+ mFocusX = cx0 + cvx * 0.5f;
+ mFocusY = cy0 + cvy * 0.5f;
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+ mCurrPressure = curr.getPressure(0) + curr.getPressure(1);
+ mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
+ }
+
+ private void reset() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mSloppyGesture = false;
+ mGestureInProgress = false;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#isInProgress()
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getFocusX()
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getFocusY()
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getCurrentSpan()
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getPreviousSpan()
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getScaleFactor()
+ */
+ public float getScaleFactor() {
+ if (mScaleFactor == -1) {
+ mScaleFactor = getCurrentSpan() / getPreviousSpan();
+ }
+ return mScaleFactor;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getTimeDelta()
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.android.bc.IBCScaleGestureDetector#getEventTime()
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+}
diff --git a/app/src/main/java/com/antlersoft/android/bc/SimpleOnScaleGestureListener.java b/app/src/main/java/com/antlersoft/android/bc/SimpleOnScaleGestureListener.java
new file mode 100644
index 000000000..1e0bbc6e1
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/bc/SimpleOnScaleGestureListener.java
@@ -0,0 +1,61 @@
+/* Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * MODIFIED FOR ANTLERSOFT
+ *
+ * Changes for antlersoft/ vnc viewer for android
+ *
+ * Copyright (C) 2010 Michael A. MacDonald
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+package com.antlersoft.android.bc;
+
+/**
+ * A convenience class to extend when you only want to listen for a subset
+ * of scaling-related events. This implements all methods in
+ * {@link OnScaleGestureListener} but does nothing.
+ * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
+ * {@code false} so that a subclass can retrieve the accumulated scale
+ * factor in an overridden onScaleEnd.
+ * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
+ * {@code true}.
+ */
+public class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+ public boolean onScale(IBCScaleGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onScaleBegin(IBCScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(IBCScaleGestureDetector detector) {
+ // Intentionally empty
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/antlersoft/android/drawing/OverlappingCopy.java b/app/src/main/java/com/antlersoft/android/drawing/OverlappingCopy.java
new file mode 100644
index 000000000..96e15fa2b
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/drawing/OverlappingCopy.java
@@ -0,0 +1,136 @@
+package com.antlersoft.android.drawing;
+
+import com.antlersoft.util.ObjectPool;
+import com.antlersoft.util.SafeObjectPool;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public class OverlappingCopy
+{
+ private static SafeObjectPool ocRectPool = new SafeObjectPool() {
+ @Override
+ protected Rect itemForPool()
+ {
+ return new Rect();
+ }
+ };
+ private static void transformRect(Rect source, Rect transformedSource, int deltaX, int deltaY)
+ {
+ transformedSource.set(deltaX < 0 ? source.right * -1 : source.left,
+ deltaY < 0 ? source.bottom * -1 : source.top,
+ deltaX < 0 ? source.left * -1 : source.right,
+ deltaY < 0 ? source.top * -1 : source.bottom);
+ }
+ private static void copyTransformedRect(Rect stepSourceRect, Rect stepDestRect, int deltaX, int deltaY, Bitmap data, Canvas bitmapBackedCanvas, Paint paint)
+ {
+ transformRect(stepSourceRect,stepSourceRect,deltaX,deltaY);
+ stepDestRect.set(stepSourceRect);
+ stepDestRect.offset(deltaX,deltaY);
+ bitmapBackedCanvas.drawBitmap(data, stepSourceRect, stepDestRect, paint);
+ }
+ public static void Copy(Bitmap data, Canvas bitmapBackedCanvas, Paint paint, Rect source, int destX, int destY)
+ {
+ Copy(data,bitmapBackedCanvas,paint,source,destX,destY,ocRectPool);
+ }
+ public static void Copy(Bitmap data, Canvas bitmapBackedCanvas, Paint paint, Rect source, int destX, int destY, ObjectPool rectPool)
+ {
+ //android.util.Log.i("LBM","Copy "+source.toString()+" to "+destX+","+destY);
+ int deltaX = destX - source.left;
+ int deltaY = destY - source.top;
+ int absDeltaX = deltaX < 0 ? -deltaX : deltaX;
+ int absDeltaY = deltaY < 0 ? -deltaY : deltaY;
+
+ // Look for degenerate case
+ if (absDeltaX == 0 && absDeltaY == 0)
+ return;
+ // Look for non-overlap case
+ if (absDeltaX >= source.right - source.left || absDeltaY >= source.bottom - source.top)
+ {
+ // Non-overlapping copy
+ ObjectPool.Entry entry = rectPool.reserve();
+ Rect dest = entry.get();
+ dest.set(source.left + deltaX, source.top + deltaY, source.right + deltaX, source.bottom + deltaY);
+ bitmapBackedCanvas.drawBitmap(data, source, dest, paint);
+ rectPool.release(entry);
+ return;
+ }
+ // Determine coordinate transform so that dest rectangle is always down and to the right.
+ ObjectPool.Entry transformedSourceEntry = rectPool.reserve();
+ Rect transformedSource = transformedSourceEntry.get();
+ transformRect(source,transformedSource,deltaX,deltaY);
+ ObjectPool.Entry transformedDestEntry = rectPool.reserve();
+ Rect transformedDest = transformedDestEntry.get();
+ transformedDest.set(transformedSource);
+ transformedDest.offset(absDeltaX, absDeltaY);
+ ObjectPool.Entry intersectEntry = rectPool.reserve();
+ Rect intersect = intersectEntry.get();
+ intersect.setIntersect(transformedSource, transformedDest);
+
+ boolean xStepDone = false;
+ int xStepWidth;
+ int yStepHeight;
+ if (absDeltaX > absDeltaY)
+ {
+ xStepWidth = absDeltaX;
+ yStepHeight = source.bottom - source.top - absDeltaY;
+ }
+ else
+ {
+ xStepWidth = source.right - source.left - absDeltaX;
+ yStepHeight = absDeltaY;
+ }
+
+ ObjectPool.Entry stepSourceEntry = rectPool.reserve();
+ Rect stepSourceRect = stepSourceEntry.get();
+ ObjectPool.Entry stepDestEntry = rectPool.reserve();
+ Rect stepDestRect = stepDestEntry.get();
+
+ for (int xStep = 0; ! xStepDone; xStep++)
+ {
+ int stepRight = intersect.right - xStep * xStepWidth;
+ int stepLeft = stepRight - xStepWidth;
+ if (stepLeft <= intersect.left)
+ {
+ stepLeft = intersect.left;
+ xStepDone = true;
+ }
+ boolean yStepDone = false;
+ for (int yStep = 0; ! yStepDone; yStep++)
+ {
+ int stepBottom = intersect.bottom - yStep * yStepHeight;
+ int stepTop = stepBottom - yStepHeight;
+ if (stepTop <= intersect.top)
+ {
+ stepTop = intersect.top;
+ yStepDone = true;
+ }
+ stepSourceRect.set(stepLeft,stepTop,stepRight,stepBottom);
+ //android.util.Log.i("LBM","Copy transformed "+stepSourceRect.toString()+" "+deltaX+" "+deltaY);
+ copyTransformedRect(stepSourceRect, stepDestRect, deltaX, deltaY, data, bitmapBackedCanvas, paint);
+ }
+ }
+ if (absDeltaX>0)
+ {
+ // Copy left edge
+ stepSourceRect.set(transformedSource.left,transformedSource.top,intersect.left,transformedSource.bottom);
+ copyTransformedRect(stepSourceRect, stepDestRect, deltaX, deltaY, data, bitmapBackedCanvas, paint);
+ }
+ if (absDeltaY>0)
+ {
+ // Copy top excluding left edge
+ stepSourceRect.set(intersect.left,transformedSource.top,transformedSource.right,intersect.top);
+ copyTransformedRect(stepSourceRect, stepDestRect, deltaX, deltaY, data, bitmapBackedCanvas, paint);
+ }
+
+ rectPool.release(stepDestEntry);
+ rectPool.release(stepSourceEntry);
+ rectPool.release(intersectEntry);
+ rectPool.release(transformedDestEntry);
+ rectPool.release(transformedSourceEntry);
+ }
+}
+
+
diff --git a/app/src/main/java/com/antlersoft/android/drawing/RectList.java b/app/src/main/java/com/antlersoft/android/drawing/RectList.java
new file mode 100644
index 000000000..27212c816
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/android/drawing/RectList.java
@@ -0,0 +1,549 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.android.drawing;
+
+import android.graphics.Rect;
+
+import java.util.ArrayList;
+
+import com.antlersoft.util.ObjectPool;
+
+/**
+ * A list of rectangular regions that together represent an area of interest. Provides
+ * a set of operations that apply to the whole area, adding, changing and mutating the
+ * rectangles in the list as required.
+ *
+ * Invariants: None of the rectangles in the list overlap; no pair of rectangles in the list
+ * together make a single rectangle (none share a complete side)
+ *
+ *
+ * Instances of this class are not thread safe
+ *
+ * @author Michael A. MacDonald
+ *
+ */
+public class RectList {
+
+ enum OverlapType {
+ NONE,
+ SAME,
+ CONTAINS,
+ CONTAINED_BY,
+ COALESCIBLE,
+ PARTIAL
+ }
+
+ static final int LEFT = 1;
+ static final int TOP_LEFT = 2;
+ static final int TOP = 4;
+ static final int TOP_RIGHT = 8;
+ static final int RIGHT = 16;
+ static final int BOTTOM_RIGHT = 32;
+ static final int BOTTOM = 64;
+ static final int BOTTOM_LEFT = 128;
+
+ /**
+ * The part left over when one rectangle is subtracted from another
+ * @author Michael A. MacDonald
+ *
+ */
+ static class NonOverlappingPortion
+ {
+ Rect leftPortion;
+ Rect topLeftPortion;
+ Rect topPortion;
+ Rect topRightPortion;
+ Rect rightPortion;
+ Rect bottomRightPortion;
+ Rect bottomPortion;
+ Rect bottomLeftPortion;
+
+ int r1Owns;
+ int r2Owns;
+ int common;
+ int adjacent;
+ boolean horizontalOverlap;
+ boolean verticalOverlap;
+
+ Rect coalesced;
+
+ NonOverlappingPortion()
+ {
+ leftPortion = new Rect();
+ topLeftPortion = new Rect();
+ topPortion = new Rect();
+ topRightPortion = new Rect();
+ rightPortion = new Rect();
+ bottomRightPortion = new Rect();
+ bottomPortion = new Rect();
+ bottomLeftPortion = new Rect();
+ coalesced = new Rect();
+ }
+
+ void setCornerOwnership(int side1, int side2, int corner)
+ {
+ int combined = (side1 | side2);
+ if ((r1Owns & combined) == combined)
+ r1Owns |= corner;
+ else if ((r2Owns & combined) == combined)
+ r2Owns |= corner;
+ }
+
+ void setCornerOwnership()
+ {
+ setCornerOwnership(LEFT,TOP,TOP_LEFT);
+ setCornerOwnership(TOP,RIGHT,TOP_RIGHT);
+ setCornerOwnership(BOTTOM,RIGHT,BOTTOM_RIGHT);
+ setCornerOwnership(BOTTOM,LEFT,BOTTOM_LEFT);
+ }
+
+ /**
+ * Populates with the borders remaining when r2 is subtracted from r1
+ * @param r1
+ * @param r2
+ * @return
+ */
+ OverlapType overlap(Rect r1, Rect r2)
+ {
+ r1Owns = 0;
+ r2Owns = 0;
+ common = 0;
+ adjacent = 0;
+ OverlapType result = OverlapType.NONE;
+ horizontalOverlap = false;
+ verticalOverlap = false;
+
+ if (r1.left < r2.left)
+ {
+ leftPortion.left = topLeftPortion.left = bottomLeftPortion.left = r1.left;
+ if (r2.left < r1.right) {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r2.left;
+ horizontalOverlap = true;
+ } else {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r1.right;
+ if (r2.left == r1.right)
+ adjacent |= LEFT;
+ }
+ r1Owns |= LEFT;
+ }
+ else
+ {
+ leftPortion.left = topLeftPortion.left = bottomLeftPortion.left = r2.left;
+ if (r1.left < r2.right) {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r1.left;
+ horizontalOverlap = true;
+ } else {
+ leftPortion.right = topLeftPortion.right = bottomLeftPortion.right = topPortion.left = bottomPortion.left = r2.right;
+ if ( r1.left == r2.right)
+ adjacent |= RIGHT;
+ }
+ if (r2.left < r1.left)
+ r2Owns |= LEFT;
+ else
+ common |= LEFT;
+ }
+ if (r1.top < r2.top)
+ {
+ topPortion.top = topLeftPortion.top = topRightPortion.top = r1.top;
+ if (r2.top < r1.bottom) {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r2.top;
+ verticalOverlap = true;
+ } else {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r1.bottom;
+ if (r2.top == r1.bottom)
+ adjacent |= TOP;
+ }
+ r1Owns |= TOP;
+ }
+ else
+ {
+ topPortion.top = topLeftPortion.top = topRightPortion.top = r2.top;
+ if (r1.top < r2.bottom) {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r1.top;
+ verticalOverlap = true;
+ } else {
+ topPortion.bottom = topLeftPortion.bottom = topRightPortion.bottom = leftPortion.top = rightPortion.top = r2.bottom;
+ if (r1.top == r2.bottom)
+ adjacent |= BOTTOM;
+ }
+ if (r2.top < r1.top)
+ r2Owns |= TOP;
+ else
+ common |= TOP;
+ }
+ if (r1.right > r2.right)
+ {
+ rightPortion.right = topRightPortion.right = bottomRightPortion.right = r1.right;
+ if (r2.right > r1.left) {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r2.right;
+ horizontalOverlap = true;
+ } else {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r1.left;
+ if (r2.right == r1.left)
+ adjacent |= RIGHT;
+ }
+ r1Owns |= RIGHT;
+ }
+ else
+ {
+ rightPortion.right = topRightPortion.right = bottomRightPortion.right = r2.right;
+ if (r1.right > r2.left) {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r1.right;
+ horizontalOverlap = true;
+ } else {
+ rightPortion.left = topRightPortion.left = bottomRightPortion.left = topPortion.right = bottomPortion.right = r2.left;
+ if (r1.right==r2.left)
+ adjacent |= LEFT;
+ }
+ if (r2.right > r1.right)
+ r2Owns |= RIGHT;
+ else
+ common |= RIGHT;
+ }
+ if (r1.bottom > r2.bottom)
+ {
+ bottomPortion.bottom = bottomLeftPortion.bottom = bottomRightPortion.bottom = r1.bottom;
+ if (r2.bottom > r1.top) {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r2.bottom;
+ verticalOverlap = true;
+ } else {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r1.top;
+ if (r2.bottom==r1.top)
+ adjacent |= BOTTOM;
+ }
+ r1Owns |= BOTTOM;
+ }
+ else
+ {
+ bottomPortion.bottom = bottomLeftPortion.bottom = bottomRightPortion.bottom = r2.bottom;
+ if (r1.bottom > r2.top) {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r1.bottom;
+ verticalOverlap = true;
+ } else {
+ bottomPortion.top = bottomLeftPortion.top = bottomRightPortion.top = leftPortion.bottom = rightPortion.bottom = r2.top;
+ if (r1.bottom==r2.top)
+ adjacent |= TOP;
+ }
+ if (r2.bottom > r1.bottom)
+ r2Owns |= BOTTOM;
+ else
+ common |= BOTTOM;
+ }
+ if ( common == (LEFT|RIGHT|TOP|BOTTOM))
+ {
+ result = OverlapType.SAME;
+ }
+ else if ((common & (LEFT|RIGHT)) == (LEFT | RIGHT) && (verticalOverlap || (adjacent & (TOP | BOTTOM)) != 0))
+ {
+ result = OverlapType.COALESCIBLE;
+ coalesced.left = r1.left;
+ coalesced.right = r1.right;
+ coalesced.top = topPortion.top;
+ coalesced.bottom = bottomPortion.bottom;
+ }
+ else if ((common & (TOP | BOTTOM)) == (TOP | BOTTOM) && (horizontalOverlap || (adjacent & (LEFT | RIGHT)) != 0))
+ {
+ result = OverlapType.COALESCIBLE;
+ coalesced.left = leftPortion.left;
+ coalesced.right = rightPortion.right;
+ coalesced.top = r1.top;
+ coalesced.bottom = r1.bottom;
+ }
+ else if (verticalOverlap && horizontalOverlap) {
+ if (r2Owns == 0)
+ {
+ result = OverlapType.CONTAINED_BY;
+ }
+ else if (r1Owns == 0)
+ {
+ result = OverlapType.CONTAINS;
+ }
+ else
+ {
+ // Partial overlap, non coalescible case
+ result = OverlapType.PARTIAL;
+ setCornerOwnership();
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Up to 8 Rect objects
+ * @author Michael A. MacDonald
+ *
+ */
+ static class NonOverlappingRects
+ {
+ ObjectPool.Entry[] rectEntries;
+ int count;
+ static final int MAX_RECTS = 8;
+
+ NonOverlappingRects()
+ {
+ rectEntries = new ObjectPool.Entry[MAX_RECTS];
+ }
+
+ private void addOwnedRect(int owner, int direction, ObjectPool pool, Rect r)
+ {
+ if ((owner & direction)==direction)
+ {
+ ObjectPool.Entry entry = pool.reserve();
+ rectEntries[count++] = entry;
+ entry.get().set(r);
+ }
+ }
+
+ void Populate(NonOverlappingPortion p, ObjectPool pool, int owner)
+ {
+ count = 0;
+ for (int i=0; i> list;
+ private ObjectPool pool;
+ private ObjectPool nonOverlappingRectsPool = new ObjectPool() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#itemForPool()
+ */
+ @Override
+ protected NonOverlappingRects itemForPool() {
+ return new NonOverlappingRects();
+ }
+
+ };
+ private ObjectPool>> listRectsPool = new ObjectPool>>() {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#itemForPool()
+ */
+ @Override
+ protected ArrayList> itemForPool() {
+ return new ArrayList>(NonOverlappingRects.MAX_RECTS);
+ }
+ };
+ private NonOverlappingPortion nonOverlappingPortion;
+
+ public RectList(ObjectPool pool)
+ {
+ this.pool = pool;
+ list = new ArrayList>();
+ nonOverlappingPortion = new NonOverlappingPortion();
+ }
+
+ public int getSize()
+ {
+ return list.size();
+ }
+
+ public Rect get(int i)
+ {
+ return list.get(i).get();
+ }
+
+ /**
+ * Remove all rectangles from the list and release them from the pool
+ */
+ public void clear()
+ {
+ for (int i=list.size()-1; i>=0; i--)
+ {
+ ObjectPool.Entry r = list.get(i);
+ pool.release(r);
+ }
+ list.clear();
+ }
+
+ private void recursiveAdd(ObjectPool.Entry toAdd, int level)
+ {
+ if (level>=list.size())
+ {
+ list.add(toAdd);
+ return;
+ }
+ Rect addRect = toAdd.get();
+ ObjectPool.Entry thisEntry = list.get(level);
+ Rect thisRect = thisEntry.get();
+ switch (nonOverlappingPortion.overlap(thisRect, addRect))
+ {
+ case NONE :
+ recursiveAdd(toAdd,level + 1);
+ break;
+ case SAME :
+ case CONTAINS :
+ pool.release(toAdd);
+ break;
+ case CONTAINED_BY :
+ pool.release(thisEntry);
+ list.remove(level);
+ recursiveAdd(toAdd,level);
+ break;
+ case COALESCIBLE :
+ pool.release(thisEntry);
+ list.remove(level);
+ addRect.set(nonOverlappingPortion.coalesced);
+ recursiveAdd(toAdd,0);
+ break;
+ case PARTIAL :
+ pool.release(toAdd);
+ ObjectPool.Entry rectsEntry = nonOverlappingRectsPool.reserve();
+ NonOverlappingRects rects = rectsEntry.get();
+ rects.Populate(nonOverlappingPortion,pool,nonOverlappingPortion.r2Owns);
+ for (int i=0; i entry = pool.reserve();
+ Rect r = entry.get();
+ r.set(toAdd);
+ recursiveAdd(entry,0);
+ }
+
+ /**
+ * Change the rectangle of interest to include only those portions
+ * that fall inside bounds.
+ * @param bounds
+ */
+ public void intersect(Rect bounds)
+ {
+ int size = list.size();
+ ObjectPool.Entry>> listEntry = listRectsPool.reserve();
+ ArrayList> newList = listEntry.get();
+ newList.clear();
+ for (int i=0; i entry = list.get(i);
+ Rect rect = entry.get();
+ if (rect.intersect(bounds))
+ {
+ newList.add(entry);
+ }
+ else
+ pool.release(entry);
+ }
+ list.clear();
+ size = newList.size();
+ for (int i=0; i>> listEntry = listRectsPool.reserve();
+ ArrayList> newList = listEntry.get();
+ newList.clear();
+ for (int i=0; i entry = list.get(i);
+ Rect rect = entry.get();
+ switch (nonOverlappingPortion.overlap(rect, toSubtract))
+ {
+ case SAME:
+ pool.release(entry);
+ newList.clear();
+ list.remove(i);
+ return;
+ case CONTAINED_BY:
+ pool.release(entry);
+ list.remove(i);
+ i--;
+ size--;
+ break;
+ case NONE:
+ break;
+ case COALESCIBLE:
+ if (!nonOverlappingPortion.verticalOverlap || ! nonOverlappingPortion.horizontalOverlap)
+ break;
+ case CONTAINS :
+ nonOverlappingPortion.setCornerOwnership();
+ case PARTIAL :
+ {
+ ObjectPool.Entry rectsEntry = nonOverlappingRectsPool.reserve();
+ NonOverlappingRects rects = rectsEntry.get();
+ rects.Populate(nonOverlappingPortion, pool, nonOverlappingPortion.r1Owns);
+ pool.release(entry);
+ list.remove(i);
+ i--;
+ size--;
+ for (int j=0; j m_max_size)
+ {
+ m_max_size = Math.max(2 * m_max_size, new_size);
+ byte[] new_buffer = new byte[m_max_size];
+ System.arraycopy(m_buffer, 0, new_buffer, 0, result);
+ m_buffer = new_buffer;
+ }
+
+ return result;
+ }
+
+ public void release()
+ {
+ if (m_depth<1)
+ {
+ throw new IllegalStateException("release() without reserve()");
+ }
+ m_depth--;
+ }
+}
diff --git a/app/src/main/java/com/antlersoft/util/ObjectPool.java b/app/src/main/java/com/antlersoft/util/ObjectPool.java
new file mode 100644
index 000000000..e5043096f
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/util/ObjectPool.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.util;
+
+/**
+ * A pool of reusable object of a given type. You get the object from a Entry, which you get
+ * by calling reserve(). When you are done with the object, you call release() passing the Entry.
+ *
+ * Failing to call release() does not leak memory--but you will not get the benefits
+ * of reusing the object. You will run into contention issues if you
+ * call release() while still holding a reference to the pool object.
+ * @author Michael A. MacDonald
+ *
+ */
+public abstract class ObjectPool {
+ public static class Entry {
+ S item;
+ Entry nextEntry;
+
+ Entry(S i, Entry n)
+ {
+ item = i;
+ nextEntry = n;
+ }
+
+ public S get() {
+ return item;
+ }
+ }
+
+ private Entry next;
+ public ObjectPool()
+ {
+ next = null;
+ }
+
+ public Entry reserve()
+ {
+ if (next == null)
+ {
+ next = new Entry(itemForPool(), null);
+ }
+ Entry result = next;
+ next = result.nextEntry;
+ result.nextEntry = null;
+
+ return result;
+ }
+
+ public void release(Entry entry)
+ {
+ entry.nextEntry = next;
+ next = entry;
+ }
+
+ protected abstract R itemForPool();
+}
diff --git a/app/src/main/java/com/antlersoft/util/SafeObjectPool.java b/app/src/main/java/com/antlersoft/util/SafeObjectPool.java
new file mode 100644
index 000000000..3d197ab0c
--- /dev/null
+++ b/app/src/main/java/com/antlersoft/util/SafeObjectPool.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2009 Michael A. MacDonald
+ */
+package com.antlersoft.util;
+
+/**
+ * Synchronized object pool
+ * @author Michael A. MacDonald
+ *
+ */
+public abstract class SafeObjectPool extends ObjectPool {
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#release(com.antlersoft.util.ObjectPool.Entry)
+ */
+ @Override
+ public synchronized void release(com.antlersoft.util.ObjectPool.Entry entry) {
+ super.release(entry);
+ }
+
+ /* (non-Javadoc)
+ * @see com.antlersoft.util.ObjectPool#reserve()
+ */
+ @Override
+ public synchronized com.antlersoft.util.ObjectPool.Entry reserve() {
+ return super.reserve();
+ }
+
+}
diff --git a/app/src/main/java/com/theqvd/android/client/package-info.java b/app/src/main/java/com/theqvd/android/client/package-info.java
new file mode 100644
index 000000000..ac1995787
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/client/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Dummy package
+ */
+/**
+ * @author nito
+ *
+ */
+package com.theqvd.android.client;
\ No newline at end of file
diff --git a/app/src/main/java/com/theqvd/android/xpro/Config.java b/app/src/main/java/com/theqvd/android/xpro/Config.java
new file mode 100644
index 000000000..23dcd1f75
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/xpro/Config.java
@@ -0,0 +1,352 @@
+/**
+ * Singleton Class to hold all the configuration strings
+ *
+ * Copyright 2009-2014 by Qindel Formacion y Servicios S.L.
+ *
+ * xvncpro is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xvncpro is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+package com.theqvd.android.xpro;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+import net.kdt.pojavlaunch.*;
+/**
+ *
+ * Class to hold all the configuration strings + the persistent configuration
+ * of the application stored in the property files
+ *
+ * @author nito
+ *
+ */
+
+public class Config {
+ public final static String specialAndroid22Extension = ".ogg";
+ public final static String assetscopydir = "xserver";
+ public final static String vnccmd = "vnc://localhost:5900/C24bit/ben1to";
+ public final static String x11cmd = "x11://localhost:6000";
+ private static String targetdir;
+ public static String xvnc;
+ public static String xvnccmd;
+ public static String pocketvncconfigfullpath;
+
+// public final static String xvnc = targetdir + "/usr/X11R6/bin/" + xvncbinary;
+// public final static String xvnccmd = xvnc + " :0 -br -localhost -nolisten local -PasswordFile="+targetdir+"/etc/vncpasswd";
+ public static String xvncbinary = L.xvncbinary;
+ public final static String notAllowRemoteVncConns = "-localhost";
+ public final static String psxvnccmd = "/system/bin/ps "+L.xvncbinary;
+ public final static String serverstartedstring = "^.*?created VNC server for screen 0";
+ public final static String vncdisconnectedstring = ".*?Connections: closed: 127.0.0.1.*";
+ // Connections: closed: 127.0.0.1::51506
+ // Property strings in the property file
+ public final static String props_hasbeencopied = "hasbeencopied";
+ public final static String props_pocketconfigcopied = "pocketconfigcopied";
+ public final static String props_forcexresolution = "forcexresolution";
+ public final static String props_widthpixels = "widthpixels";
+ public final static String props_heightpixels = "heightpixels";
+ public final static String props_keep_x_running = "keepxrunning";
+ public final static String props_use_android_vnc = "useandroidvnc";
+ public final static String props_remote_vnc = "useremotevnc";
+ public final static String props_render = "userender";
+ public final static String props_xinerama = "usexinerama";
+ public final static String helpurl = "http://docs.theqvd.com/";
+ public final static int minPixels = 32;
+ public final static int maxPixels = 10000;
+ public final static boolean debug = false;
+ public final static int notifycopy = 1;
+ public final static int notifystartx = 2;
+ public final static int notifynovncinstalled = 3;
+ public final static int xvncsizerequired = 40; /* 37 MB required */
+ public final static long xvncsizerequiredinkbytes = xvncsizerequired * 1024L;
+ public final static String pocketvncconfig = "xvnc.vnc";
+// public static String pocketvncconfigfullpath = targetdir + Config.pocketvncconfig;
+ public final static int INSTALLPACKAGE=1;
+ public final static int SENDALERT=0;
+ public final static int SETCOPYPROGRESS=1;
+ public final static int SETPROGRESSVISIBILITY=2;
+ public final static int UPDATEBUTTONS=3;
+ public final static int PRERREQUISITEINSTALLED=4;
+ public final static int[] messageType = {
+ SENDALERT, // uses messageTitle and messageText in the setData
+ SETCOPYPROGRESS, // uses progress in the setData
+ SETPROGRESSVISIBILITY, // uses progressvisibility in the setData
+ UPDATEBUTTONS, // no parameters
+ PRERREQUISITEINSTALLED, // no parameters
+ };
+ public final static String messageTitle = "title";
+ public final static String messageText = "text";
+ public final static String copyProgress = "progress";
+ public final static String progressVisibility = "progressVisibility";
+ // StartActivityForResult codes
+ public final static int vncActivityRequestCode = 11;
+
+
+ public static String getAbout(String version) {
+ return "XVnc\nLicense: Licensed under the GPLv3.\nAuthor: support@theqvd.com\nSponsored: http://theqvd.com\nVersion: "+version+"\nRevision: $Revision: 26639 $\nDate: $Date: 2015-03-31 11:51:02 +0200 (Tue, 31 Mar 2015) $";
+ }
+ // Class info
+ static final String tag = L.xvncbinary + "-Config-" +java.util.Map.Entry.class.getSimpleName();
+ private static Context context;
+ private static Activity activity;
+ private static boolean appConfig_force_x_geometry = false,
+ appConfig_keep_x_running = false,
+ appConfig_remote_vnc_allowed = false,
+ appConfig_render = true,
+ appConfig_xinerama = false;
+ private static int appConfig_height_pixels = 0, appConfig_width_pixels = 0,
+ appConfig_defaultHeightPixels = 0, appconfig_defaultWidthPixels = 0;
+ private static VncViewerAndroid androidvncviewer;
+ private static Handler uiHandler;
+ // private Prerrequisite[] prerrequisites;
+ // Set installPrerrequisitesOnStart to true if you want to finish the activity
+ // after installation
+ private static boolean installPrerrequisitesOnStart = false;
+
+ private void init() {
+ Log.i(tag, "The CPU type from CPU_ABI is "+android.os.Build.CPU_ABI);
+ setTargetdir(Tools.datapath + "/xvncfiles");
+ pocketvncconfigfullpath = getTargetdir() + "/" + Config.pocketvncconfig;
+ xvnc = getTargetdir() + "/usr/X11R6/bin/" + L.xvncbinary;
+ if (android.os.Build.CPU_ABI.equals("x86")) {
+ xvnc += "i386";
+ } else if (android.os.Build.CPU_ABI.startsWith("arm")) {
+ // do not do anything
+ } else {
+ Log.e(tag, "Unknown CPU_ABI is neither x86 and not arm*");
+ // TODO throw error here?
+ }
+ //xvnc += specialAndroid22Extension;
+
+
+ xvnccmd = xvnc + " :0 -br -nolisten local -pixelformat rgb888 -pixdepths 1 4 8 15 16 24 32 -PasswordFile="+getTargetdir()+"/etc/vncpasswd";
+ setHeightAndWidth();
+ load_properties();
+ }
+
+ public Config(Context c) {
+ context = c;
+ init();
+ }
+ public Config(Activity a) {
+ context = a;
+ activity = a;
+ init();
+ }
+
+ private void setHeightAndWidth() {
+ // Set height and width
+ WindowManager w = activity.getWindowManager();
+ Display d = w.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ d.getMetrics(metrics);
+ // since SDK_INT = 1;
+ int widthPixels = metrics.widthPixels;
+ int heightPixels = metrics.heightPixels;
+ Log.d(tag, "setHeightAndWidth:The Build.VERSION is:"+Build.VERSION.SDK_INT+
+ " and the initial width and height is:"+widthPixels+","+heightPixels);
+
+ // includes window decorations (statusbar bar/menu bar)
+ if (Build.VERSION.SDK_INT >= 17)
+ try {
+ Log.d(tag, "setHeightAndWidth:The Build.VERSION is greater than 17:"+Build.VERSION.SDK_INT);
+ Point realSize = new Point();
+ Display.class.getMethod("getSize", Point.class).invoke(d, realSize); //getRealSize gets full screen without decorations
+// Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize);
+ widthPixels = realSize.x;
+ heightPixels = realSize.y;
+ Log.d(tag, "setHeightAndWidth:The Build.VERSION is greater than 17:"+Build.VERSION.SDK_INT+
+ " and the width and height is:"+widthPixels+","+heightPixels);
+ } catch (Exception ignored) {
+ }
+
+ // force landscape hack
+ appConfig_height_pixels = appConfig_defaultHeightPixels = (heightPixels > widthPixels) ? widthPixels : heightPixels;
+ appConfig_width_pixels = appconfig_defaultWidthPixels = (heightPixels > widthPixels) ? heightPixels : widthPixels;
+ Log.d(tag, "setHeightAndWidth: The final and the end width and height is:"+
+ appconfig_defaultWidthPixels+","+appConfig_defaultHeightPixels);
+ }
+
+
+ private void load_properties() {
+ // Use default settings
+ // appConfig_force_x_geometry = true;
+ // appConfig_keep_x_running = true;
+
+ /*
+ SharedPreferences prefsPrivate;
+ prefsPrivate = context.getSharedPreferences("PREFS_PRIVATE", Context.MODE_PRIVATE);
+ appConfig_force_x_geometry = prefsPrivate.getBoolean(Config.props_forcexresolution, appConfig_force_x_geometry);
+ appConfig_keep_x_running = prefsPrivate.getBoolean(Config.props_keep_x_running, appConfig_keep_x_running);
+ appConfig_run_androidvnc_client = prefsPrivate.getBoolean(Config.props_use_android_vnc, appConfig_run_androidvnc_client);
+ appConfig_xvncbinary_copied = prefsPrivate.getBoolean(Config.props_hasbeencopied, appConfig_xvncbinary_copied);
+ appConfig_height_pixels = prefsPrivate.getInt(Config.props_heightpixels, appConfig_defaultHeightPixels);
+ appConfig_width_pixels = prefsPrivate.getInt(Config.props_widthpixels, appconfig_defaultWidthPixels);
+ appConfig_pocketconfig_copied = prefsPrivate.getBoolean(Config.props_pocketconfigcopied, appConfig_pocketconfig_copied);
+ appConfig_remote_vnc_allowed = prefsPrivate.getBoolean(Config.props_remote_vnc, appConfig_remote_vnc_allowed);
+ appConfig_render = prefsPrivate.getBoolean(Config.props_render, appConfig_render);
+ appConfig_xinerama = prefsPrivate.getBoolean(Config.props_xinerama, appConfig_xinerama);
+ */
+ }
+ private void save_properties() {
+ /*
+ SharedPreferences prefsPrivate;
+ prefsPrivate = context.getSharedPreferences("PREFS_PRIVATE", Context.MODE_PRIVATE);
+ Editor prefsPrivateEditor = prefsPrivate.edit();
+ prefsPrivateEditor.putBoolean(Config.props_forcexresolution, appConfig_force_x_geometry);
+ prefsPrivateEditor.putBoolean(Config.props_keep_x_running, appConfig_keep_x_running);
+ prefsPrivateEditor.putBoolean(Config.props_use_android_vnc, appConfig_run_androidvnc_client);
+ prefsPrivateEditor.putBoolean(Config.props_hasbeencopied, appConfig_xvncbinary_copied);
+ prefsPrivateEditor.putBoolean(Config.props_pocketconfigcopied, appConfig_pocketconfig_copied);
+ prefsPrivateEditor.putBoolean(Config.props_remote_vnc, appConfig_remote_vnc_allowed);
+ prefsPrivateEditor.putBoolean(Config.props_render, appConfig_render);
+ prefsPrivateEditor.putBoolean(Config.props_xinerama, appConfig_xinerama);
+ prefsPrivateEditor.putInt(Config.props_heightpixels, appConfig_height_pixels);
+ prefsPrivateEditor.putInt(Config.props_widthpixels, appConfig_width_pixels);
+ prefsPrivateEditor.commit();
+ */
+ }
+ public VncViewer getVncViewer() throws XvncproException {
+ return this.getAndroidvncviewer(); // : this.getPocketcloudvncviewer();
+ }
+ public boolean is_force_x_geometry() {
+ return appConfig_force_x_geometry;
+ }
+ public void set_force_x_geometry(
+ boolean appConfig_force_x_geometry) {
+ Config.appConfig_force_x_geometry = appConfig_force_x_geometry;
+ save_properties();
+ }
+ public boolean is_keep_x_running() {
+ return appConfig_keep_x_running;
+ }
+ public void set_keep_x_running(boolean appConfig_keep_x_running) {
+ Config.appConfig_keep_x_running = appConfig_keep_x_running;
+ save_properties();
+ }
+ public boolean isAppConfig_remote_vnc_allowed() {
+ return appConfig_remote_vnc_allowed;
+ }
+ public void setAppConfig_remote_vnc_allowed(
+ boolean appConfig_remote_vnc_allowed) {
+ Config.appConfig_remote_vnc_allowed = appConfig_remote_vnc_allowed;
+ save_properties();
+ }
+ public int get_height_pixels() {
+ return appConfig_height_pixels;
+ }
+ public void set_height_pixels(int appConfig_height_pixels) {
+ Config.appConfig_height_pixels = appConfig_height_pixels;
+ save_properties();
+ }
+ public int get_width_pixels() {
+ return appConfig_width_pixels;
+ }
+ public void set_width_pixels(int appConfig_width_pixels) {
+ Config.appConfig_width_pixels = appConfig_width_pixels;
+ save_properties();
+ }
+ public int getAppConfig_defaultHeightPixels() {
+ return appConfig_defaultHeightPixels;
+ }
+ public int getAppconfig_defaultWidthPixels() {
+ return appconfig_defaultWidthPixels;
+ }
+ public boolean isAppConfig_render() {
+ return appConfig_render;
+ }
+ public void setAppConfig_render(boolean appConfig_render) {
+ Config.appConfig_render = appConfig_render;
+ save_properties();
+ }
+ public boolean isAppConfig_xinerama() {
+ return appConfig_xinerama;
+ }
+ public void setAppConfig_xinerama(boolean appConfig_xinerama) {
+ Config.appConfig_xinerama = appConfig_xinerama;
+ save_properties();
+ }
+ public VncViewerAndroid getAndroidvncviewer() throws XvncproException {
+ if (activity == null) {
+ throw new XvncproException(context.getString(L.r_xvncpro_activity_notdefined));
+ }
+ androidvncviewer = (androidvncviewer == null) ? new VncViewerAndroid(activity) : androidvncviewer;
+
+ return androidvncviewer;
+ }
+
+ public Handler getUiHandler() {
+ return uiHandler;
+ }
+ public void setUiHandler(Handler mHandler) {
+ Config.uiHandler = mHandler;
+ // getXvnccopy().setuiHandler(mHandler);
+ }
+ public boolean packageInstalled(String packagename) {
+ ApplicationInfo info;
+ try{
+ info = context.getPackageManager().getApplicationInfo(packagename, 0);
+ } catch( PackageManager.NameNotFoundException e ){
+ Log.i(tag, packagename + " is not installed");
+ return false;
+ }
+ Log.i(tag, packagename+" is already installed" + info);
+ return true;
+ }
+ public void installPackage(String packagename) {
+ Log.i(tag, "Requesting installation of "+packagename);
+ Intent goToMarket = new Intent(Intent.ACTION_VIEW).setData(Uri.parse("market://details?id="+packagename));
+// goToMarket.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (activity == null) {
+ Log.i(tag, "Calling installPackage withouth startActivityForResult because activity is null");
+ context.startActivity(goToMarket);
+ } else {
+ Log.d(tag, "Calling installPackage with startActivityForResult because activity is non null");
+ activity.startActivityForResult(goToMarket, INSTALLPACKAGE);
+ }
+ Log.d(tag, "package is installed sending prerrequisite installed for " + packagename);
+ Message m = getUiHandler().obtainMessage(Config.PRERREQUISITEINSTALLED);
+ getUiHandler().sendMessage(m);
+ }
+ public String getTargetdir() {
+ return targetdir;
+ }
+ public void setTargetdir(String targetdir) {
+ Config.targetdir = targetdir;
+ }
+ public static Activity getActivity() {
+ return activity;
+ }
+ public static void setActivity(Activity activity) {
+ Config.activity = activity;
+ }
+ public static boolean isInstallPrerrequisitesOnStart() {
+ return installPrerrequisitesOnStart;
+ }
+ public static void setInstallPrerrequisitesOnStart(
+ boolean finishAfterInstallingPrerrequisites) {
+ Config.installPrerrequisitesOnStart = finishAfterInstallingPrerrequisites;
+ }
+}
diff --git a/app/src/main/java/com/theqvd/android/xpro/L.java b/app/src/main/java/com/theqvd/android/xpro/L.java
new file mode 100644
index 000000000..c989b408d
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/xpro/L.java
@@ -0,0 +1,80 @@
+package com.theqvd.android.xpro;
+
+//import android.content.Context;
+//import com.theqvd.android.client.R;
+
+/**
+ *
+ * This class exists only to highlight the differences between xvncpro and qvdclient
+ *
+ * Copyright 2009-2014 by Qindel Formacion y Servicios S.L.
+ *
+ * xvncpro is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xvncpro is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *
+ * @author Nito@Qindel.ES
+ *
+ */
+import net.kdt.pojavlaunch.*;
+
+public final class L
+ {
+ public final static String xvncbinary = "Xvncpro";
+
+// here is double effort by rewriting R again,
+ public static final int r_dummylayout = R.layout.dummylayout;
+ public static final int r_xvncpromain = R.layout.xvncpromain;
+ public static final int r_xvncmenu = R.menu.xvncmenu;
+ public static final int r_xvncpro_activity_notdefined = R.string.xvncpro_activity_notdefined;
+
+ public static final int r_errorincopytitle = R.string.errorincopytitle;
+ public static final int r_errorincopy = R.string.errorincopy;
+ public static final int r_xvnccopy_not_enough_space = R.string.xvnccopy_not_enough_space;
+ public static final int r_xvnccopy_button_string = R.string.xvnccopy_button_string;
+ public static final int r_xvnccopy_install_string = R.string.xvnccopy_install_string;
+ public static final int r_androidvnc_button_string = R.string.androidvnc_button_string;
+ public static final int r_androidvnc_install_string = R.string.androidvnc_install_string;
+ public static final int r_pocketvnc_button_string = R.string.pocketvnc_button_string;
+ public static final int r_pocketvnc_install_string = R.string.pocketvnc_install_string;
+ public static final int r_installprereqs = R.string.installprereqs;
+ public static final int r_x11_error = R.string.x11_error;
+ public static final int r_x11_is_running = R.string.x11_is_running;
+ public static final int r_ic_xvnc = R.drawable.ic_xvnc;
+ public static final int r_connectionStartButton = R.id.connectionStartButton;
+ public static final int r_stopButton = R.id.stopButton;
+ public static final int r_editText1 = R.id.editText1;
+ public static final int r_editText2 = R.id.editText2;
+ public static final int r_consoletext = R.id.consoletext;
+ public static final int r_toggleForceResolutionButton = R.id.toggleForceResolutionButton;
+ public static final int r_stopOnVncDisconnectButton = R.id.stopOnVncDisconnectButton;
+ public static final int r_vncChoiceButton = R.id.vncChoiceButton;
+ public static final int r_allowRemoteVNCButton = R.id.allowRemoteVNCButton;
+ public static final int r_renderButton = R.id.renderButton;
+ public static final int r_xineramaButton = R.id.xineramaButton;
+ public static final int r_progressbar1 = R.id.progressbar1;
+ public static final int r_helpitem = R.id.helpitem;
+ public static final int r_aboutitem = R.id.aboutitem;
+ public static final int r_changelogitem = R.id.changelogitem;
+ public static final int r_exititem = R.id.exititem;
+ public static final int r_error_handler_message = R.string.error_handler_message;
+ public static final int r_copying = R.string.copying;
+ public static final int r_checkingfiles = R.string.checkingfiles;
+ public static final int r_connect_to_x = R.string.connect_to_x;
+ public static final int r_stopx = R.string.stopx;
+ public static final int r_launchx = R.string.launchx;
+ public static final int r_stopdisabled_not_running = R.string.stopdisabled_not_running;
+ public static final int r_xncpro_helpurl = R.string.xncpro_helpurl;
+ public static final int r_xvncpro_versionName = R.string.xvncpro_versionName;
+ public static final int r_xvncpro_changelogtitle = R.string.xvncpro_changelogtitle;
+ public static final int r_xvncpro_changelog = R.string.xvncpro_changelog;
+
+
+}
diff --git a/app/src/main/java/com/theqvd/android/xpro/VncViewer.java b/app/src/main/java/com/theqvd/android/xpro/VncViewer.java
new file mode 100644
index 000000000..e311b2452
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/xpro/VncViewer.java
@@ -0,0 +1,25 @@
+package com.theqvd.android.xpro;
+/**
+ * Copyright 2009-2014 by Qindel Formacion y Servicios S.L.
+ *
+ * xvncpro is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xvncpro is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+import android.app.Activity;
+import android.app.PendingIntent;
+
+public interface VncViewer /* extends Prerrequisite */ {
+
+ public void launchVncViewer() throws XvncproException;
+ public PendingIntent getContentVncIntent();
+ public Activity getActivity();
+ public void stopVncViewer();
+}
diff --git a/app/src/main/java/com/theqvd/android/xpro/VncViewerAndroid.java b/app/src/main/java/com/theqvd/android/xpro/VncViewerAndroid.java
new file mode 100644
index 000000000..871e5b126
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/xpro/VncViewerAndroid.java
@@ -0,0 +1,83 @@
+package com.theqvd.android.xpro;
+/**
+ * Copyright 2009-2014 by Qindel Formacion y Servicios S.L.
+ *
+ * xvncpro is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xvncpro is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+import android.app.*;
+import android.content.*;
+import android.graphics.*;
+import android.net.*;
+import android.util.*;
+import net.kdt.pojavlaunch.prefs.*;
+import android.support.v4.app.*;
+import android.androidVNC.*;
+
+public class VncViewerAndroid implements VncViewer {
+ static final String tag = L.xvncbinary + "-VncViewerAndroid-" +java.util.Map.Entry.class.getSimpleName();
+ final static String vncpackage = "android.androidVNC";
+ private static Activity activity;
+ private Config config;
+ PendingIntent contentVncIntent;
+ Intent vncIntent;
+
+ VncViewerAndroid(Activity a) {
+ activity = a;
+ config = new Config(activity);
+ String cmd = Config.vnccmd;
+ vncIntent = new Intent(a, VncCanvasActivity.class);
+ vncIntent.putExtra("x11", Uri.parse(cmd));
+
+ // multiple tasks
+ vncIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ vncIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ // Remove old Create new task
+ // vncIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ contentVncIntent = PendingIntent.getActivity(activity, 0, vncIntent, 0);
+ }
+
+ @Override
+ public void launchVncViewer() {
+ Log.i(tag, "launching vncviewer androidvnc with activity="+activity+"; vncIntent="+vncIntent);
+ Intent intent = (Intent) vncIntent.clone();
+ if (LauncherPreferences.PREF_FREEFORM) {
+ DisplayMetrics dm = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
+
+ ActivityOptionsCompat options = ActivityOptionsCompat.makeBasic();
+ Rect freeformRect = new Rect(0, 0, dm.widthPixels / 2, dm.heightPixels / 2);
+ options.setLaunchBounds(freeformRect);
+ activity.startActivityForResult(intent, Config.vncActivityRequestCode, options.toBundle());
+ } else {
+ activity.startActivityForResult(intent, Config.vncActivityRequestCode);
+ }
+ // activity.startActivityForResult(intent, Config.vncActivityRequestCode);
+ }
+
+ @Override
+ public void stopVncViewer() {
+ Log.i(tag, "Stopping activity with activity code " + Config.vncActivityRequestCode);
+
+ activity.finishActivity(Config.vncActivityRequestCode);
+ }
+ @Override
+ public PendingIntent getContentVncIntent() {
+ return contentVncIntent;
+ }
+
+ @Override
+ public Activity getActivity() {
+ return activity;
+ }
+
+}
diff --git a/app/src/main/java/com/theqvd/android/xpro/XserverService.java b/app/src/main/java/com/theqvd/android/xpro/XserverService.java
new file mode 100644
index 000000000..cea60c1c1
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/xpro/XserverService.java
@@ -0,0 +1,331 @@
+package com.theqvd.android.xpro;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ *
+ * This class represents an XserverService
+ *
+ *
+ * Copyright 2009-2014 by Qindel Formacion y Servicios S.L.
+ *
+ * xvncpro is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xvncpro is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * @author nito
+ *
+ */
+public class XserverService extends Service
+implements Runnable
+{
+ final static String tag = L.xvncbinary + "-XserverService-" +java.util.Map.Entry.class.getSimpleName();
+ private Thread aThread;
+ private static Process process;
+ static private boolean xserverrunning = false;
+ static private int pid = -1;
+ private Config config;
+ static private XserverService instance = null;
+
+ @Override
+ public void onCreate() {
+ Log.i(tag, "onCreate");
+ super.onCreate();
+ config = new Config(this);
+ instance = this;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(tag, "Received start id " + startId + ": " + intent);
+ super.onStartCommand(intent, flags, startId);
+
+ if (isRunning()) {
+ launchVNC();
+ return START_STICKY;
+ }
+ aThread = new Thread (this);
+ aThread.start();
+ return START_STICKY;
+ }
+
+ private void stopXvnc() {
+ if (isRunning()) {
+ Log.i(tag, "stop pid " + getPid());
+ process.destroy();
+ android.os.Process.killProcess(getPid());
+ setRunning(false);
+ setPid(-1);
+ android.os.Process.sendSignal(getPid(), android.os.Process.SIGNAL_QUIT);
+ updateButtons();
+ }
+ }
+ @Override
+ public void onDestroy() {
+ Log.i(tag, "onDestroy");
+ stopVNC();
+ if (isRunning()) {
+ Log.i(tag, "onDestroy xserverrunning destroy " + getPid());
+ stopXvnc();
+ }
+
+ cancelNotify();
+ updateButtons();
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ Log.i(tag, "onBind");
+ return null;
+ }
+
+ private void launchVNC() {
+ VncViewer v;
+ try {
+ v = config.getVncViewer();
+ v.launchVncViewer();
+ } catch (XvncproException e) {
+ sendNotify(getString(L.r_x11_error), "Pid:"+e.toString());
+ }
+ }
+ private void stopVNC() {
+ VncViewer v;
+ try {
+ v = config.getVncViewer();
+ v.stopVncViewer();
+ } catch (XvncproException e) {
+ sendNotify(getString(L.r_x11_error), "Pid:"+e.toString());
+ }
+ }
+ @Override
+ public void run() {
+ String cmd = Config.xvnccmd+" -geometry "+ config.get_width_pixels() + "x" + config.get_height_pixels();
+ cmd += config.isAppConfig_remote_vnc_allowed() ? "" : " " + Config.notAllowRemoteVncConns;
+ cmd += config.isAppConfig_render() ? " +render" : "";
+ cmd += config.isAppConfig_xinerama() ? " +xinerama" : "";
+ Log.i(tag, "launching:"+cmd);
+ String cmdList[] = cmd.split("[ ]+");
+ try {
+ process = new ProcessBuilder().command(cmdList).redirectErrorStream(true).start();
+ setPid(parsePid(process));
+ setRunning(true);
+ isRunning();
+ Log.i(tag, "after launch:<"+cmd+"> = "+process.toString() + "," + process.hashCode() + "," + process.getClass());
+ InputStream in = process.getInputStream();
+ InputStreamReader isr = new InputStreamReader(in);
+ BufferedReader br = new BufferedReader(isr, 128);
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ Log.d(tag, "Read string <"+line+">");
+ if (line.matches(Config.serverstartedstring)) {
+ Log.i(tag, "Found string <"+line+"> launching VNC client");
+ launchVNC();
+ updateButtons();
+ }
+ // For AndroidVNC error see
+ // See http://code.google.com/p/android-vnc-viewer/issues/detail?id=299
+ if (line.matches(Config.vncdisconnectedstring)) {
+ Log.i(tag, "Found string <"+line+">");
+ stopVNC();
+ if (!config.is_keep_x_running()) {
+ Log.i(tag, "Stopping Xvnc service");
+ stopXvnc();
+ this.stopSelf();
+ }
+ }
+ }
+ process.waitFor();
+ Log.i(tag, "Xvnc Process has died");
+ } catch (IOException e) {
+ Log.e(tag, "IOException:"+e.toString());
+ sendNotify(getString(L.r_x11_error), "Pid:"+e.toString());
+ } catch (InterruptedException e) {
+ Log.e(tag, "InterruptedException:"+e.toString());
+ sendNotify(getString(L.r_x11_error), "Pid:"+e.toString());
+ } finally {
+ if (!config.is_keep_x_running()) {
+ Log.i(tag, "Stopping Xvnc service (step 2)");
+ stopXvnc();
+ stopVNC();
+ stopSelf();
+ }
+ }
+ }
+ private static int parsePid(Process p) {
+ int pid;
+ String s = p.toString();
+ Log.d(tag, "parsePid for process String <"+s+">");
+ Pattern pattern = Pattern.compile("id=([0-9]+)[^0-9].*$");
+
+ Matcher m = pattern.matcher(s);
+ if (m.find()) {
+ Log.d(tag, "Pattern <"+pattern+"> found in string " + s + " with matching part "+m.group(1));
+ pid = Integer.parseInt(m.group(1));
+ } else
+ {
+ Log.e(tag, "Pattern <"+pattern+"> not found in string " + s + ". Trouble ahead when stopping");
+ pid = -1;
+ }
+ return pid;
+ }
+
+ public int getPid() {
+ return pid;
+ }
+ private void setPid(int pid) {
+ Log.d(tag, "Setting pid to " + pid);
+ XserverService.pid = pid;
+ }
+
+ private static int searchForXvncPid() {
+ String psoutput = "";
+ int pidfound = -1;
+ String cmd = Config.psxvnccmd;
+ String cmdList[] = cmd.split("[ ]+");
+ try {
+ process = new ProcessBuilder().command(cmdList).redirectErrorStream(true).start();
+ Log.i(tag, "after launch:<"+cmd+"> = "+process.toString() + "," + process.hashCode() + "," + process.getClass());
+ InputStream in = process.getInputStream();
+ InputStreamReader isr = new InputStreamReader(in);
+ BufferedReader br = new BufferedReader(isr);
+ String line;
+ while ((line = br.readLine()) != null) {
+ psoutput += line + "\n";
+ Log.d(tag, "Read string <"+line+">");
+ }
+ } catch (IOException e) {
+ Log.e(tag, "Error executing <"+cmd+"> "+e.toString());
+ return pidfound;
+ }
+
+ Pattern pattern = Pattern.compile("(?m)^\\S+\\s+(\\d+)\\s+.*?"+L.xvncbinary+"$");
+
+ Matcher m = pattern.matcher(psoutput);
+ if (m.find()) {
+ Log.d(tag, "Pattern <"+pattern+"> found in string <" + psoutput + "> with matching part "+m.group(1));
+ pidfound = Integer.parseInt(m.group(1));
+ } else
+ {
+ Log.d(tag, "Pattern <"+pattern+"> not found in string <" + psoutput + ">");
+ }
+ return pidfound;
+ }
+
+ public boolean isRunning() {
+ Log.d(tag, "Running is "+xserverrunning + " pid="+pid);
+ if (XserverService.xserverrunning) {
+ File file=new File("/proc/" + pid);
+ boolean exists = file.exists();
+ if (exists) {
+ sendNotify(getString(L.r_x11_is_running), "Pid:"+XserverService.pid);
+ return XserverService.xserverrunning;
+ }
+ // Pid no longer there
+ Log.i(tag, "The process was supposed to be running but pid "+pid+" is no longer there, setting running to false");
+ }
+ XserverService.pid=searchForXvncPid();
+ XserverService.xserverrunning = (XserverService.pid != -1);
+ if (XserverService.xserverrunning) {
+ sendNotify(getString(L.r_x11_is_running), "Pid:"+XserverService.pid);
+ } else {
+ cancelNotify();
+ }
+ return XserverService.xserverrunning;
+ }
+ private void setRunning(boolean value) {
+ Log.d(tag, "Setting xserverrunning to "+value);
+ XserverService.xserverrunning = value;
+ updateButtons();
+ }
+ private void updateButtons() {
+ Log.d(tag, "message updateButtons");
+ if (config.getUiHandler() == null) {
+ Log.d(tag, "message updateButtons not sent because uiHandler is null");
+ return;
+ }
+ Message m = config.getUiHandler().obtainMessage(Config.UPDATEBUTTONS);
+ config.getUiHandler().sendMessage(m);
+ }
+
+ private void sendAlert(String title, String text) {
+ Log.d(tag, "message sendAlert");
+ if (config.getUiHandler() == null) {
+ Log.d(tag, "message sendAlert not sent because uiHandler is null");
+ return;
+ }
+
+ Message m = config.getUiHandler().obtainMessage(Config.SENDALERT);
+ Bundle b = new Bundle();
+ b.putString(Config.messageTitle, title);
+ b.putString(Config.messageText, text);
+ m.setData(b);
+ config.getUiHandler().sendMessage(m);
+ }
+ public static XserverService getInstance() {
+ return instance;
+ }
+
+//@SuppressWarnings("deprecation")
+private void sendNotify(CharSequence title, CharSequence text) {
+ Context c = this.getApplicationContext();
+ // TODO try to set the DummyActivity as the intent
+// Intent dummyactivity = new Intent(this, DummyActivity.class);
+// dummyactivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+// PendingIntent i = PendingIntent.getActivity(c, 0, new Intent(), 0);
+ // End of TODO
+ PendingIntent i = null;
+ if (config == null) {
+ Log.i(tag, "Not sending notify because config is null. This should not happen");
+ return;
+ }
+ try {
+ i = config.getVncViewer().getContentVncIntent();
+ Log.i(tag, "activity is "+c+" intent is "+i);
+ } catch (XvncproException e) {
+ Log.e(tag, "Vnc intent error "+e.toString());
+ }
+
+ if (config == null) {
+ Log.i(tag, "Not sending notify because config, vncViewer, or context is null");
+ return;
+ }
+ if (i == null) {
+ Log.e(tag, "PendingIntent is null creating empty PendingIntent");
+ i = PendingIntent.getActivity(c, 0, new Intent(), 0);
+ }
+ Notification notification = new Notification(L.r_ic_xvnc, title, System.currentTimeMillis());
+ notification.setLatestEventInfo(c, title, text, i);
+ NotificationManager nm = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(Config.notifystartx, notification);
+ Log.i(tag, "Sent notify with id <"+Config.notifystartx+">title <" +title+"> and text <" + text +">");
+ }
+ public void cancelNotify() {
+ NotificationManager nm = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(Config.notifystartx);
+ Log.i(tag, "Cancelled notify with id <" +Config.notifystartx+">");
+ }
+}
diff --git a/app/src/main/java/com/theqvd/android/xpro/XvncproException.java b/app/src/main/java/com/theqvd/android/xpro/XvncproException.java
new file mode 100644
index 000000000..57f902191
--- /dev/null
+++ b/app/src/main/java/com/theqvd/android/xpro/XvncproException.java
@@ -0,0 +1,26 @@
+package com.theqvd.android.xpro;
+
+/**
+ * Copyright 2009-2014 by Qindel Formacion y Servicios S.L.
+ *
+ * xvncpro is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xvncpro is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+public class XvncproException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public XvncproException(String s) {
+ super(s);
+ }
+
+}
diff --git a/app/src/main/java/net/kdt/pojavlaunch/MCLauncherActivity.java b/app/src/main/java/net/kdt/pojavlaunch/MCLauncherActivity.java
index a147f4b32..238775fbf 100644
--- a/app/src/main/java/net/kdt/pojavlaunch/MCLauncherActivity.java
+++ b/app/src/main/java/net/kdt/pojavlaunch/MCLauncherActivity.java
@@ -7,7 +7,8 @@ import android.support.design.widget.*;
import android.support.v4.app.*;
import android.support.v4.view.*;
import android.support.v7.app.*;
-import android.util.*;
+import android.util.DisplayMetrics;
+import android.util.Log;
import android.view.*;
import android.widget.*;
import android.widget.AdapterView.*;
@@ -34,6 +35,8 @@ import android.graphics.*;
import android.content.pm.*;
import android.text.*;
import com.kdt.mcgui.*;
+import com.theqvd.android.xpro.*;
+import android.net.*;
public class MCLauncherActivity extends AppCompatActivity
{
@@ -620,26 +623,29 @@ public class MCLauncherActivity extends AppCompatActivity
crashView.setLastCrash("");
try {
- /*
- List jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
- jvmArgs.add("-Xms128M");
- jvmArgs.add("-Xmx1G");
- */
- Intent mainIntent = new Intent(MCLauncherActivity.this, MainConsoleActivity.class);
- // mainIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
- mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- mainIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ Intent vncIntent = new Intent(MCLauncherActivity.this, android.androidVNC.VncCanvasActivity.class);
+ vncIntent.putExtra("x11", Uri.parse(Config.vnccmd));
+
+ // multiple tasks
+ vncIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ vncIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+
if (LauncherPreferences.PREF_FREEFORM) {
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
- ActivityOptions options = (ActivityOptions) ActivityOptions.class.getMethod("makeBasic").invoke(null);
+ ActivityOptionsCompat options = ActivityOptionsCompat.makeBasic();
Rect freeformRect = new Rect(0, 0, dm.widthPixels / 2, dm.heightPixels / 2);
- options.getClass().getDeclaredMethod("setLaunchBounds", Rect.class).invoke(options, freeformRect);
- startActivity(mainIntent, options.toBundle());
+ options.setLaunchBounds(freeformRect);
+ startActivityForResult(vncIntent, Config.vncActivityRequestCode, options.toBundle());
} else {
- startActivity(mainIntent);
+ startActivityForResult(vncIntent, Config.vncActivityRequestCode);
}
+
+ // Intent mainIntent = new Intent(MCLauncherActivity.this, MainConsoleActivity.class);
+ // mainIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
+
+
}
catch (Throwable e) {
Tools.showError(MCLauncherActivity.this, e);
@@ -654,12 +660,11 @@ public class MCLauncherActivity extends AppCompatActivity
mTask = null;
}
- private Gson gsonss = gson;
public static final String MINECRAFT_RES = "http://resources.download.minecraft.net/";
public JAssets downloadIndex(String versionName, File output) throws Exception {
String versionJson = DownloadUtils.downloadString("http://s3.amazonaws.com/Minecraft.Download/indexes/" + versionName + ".json");
- JAssets version = gsonss.fromJson(versionJson, JAssets.class);
+ JAssets version = gson.fromJson(versionJson, JAssets.class);
output.getParentFile().mkdirs();
Tools.write(output.getAbsolutePath(), versionJson.getBytes(Charset.forName("UTF-8")));
return version;
diff --git a/app/src/main/java/net/kdt/pojavlaunch/MainConsoleActivity.java b/app/src/main/java/net/kdt/pojavlaunch/MainConsoleActivity.java
index fc5c7dc03..dd6ed0e30 100644
--- a/app/src/main/java/net/kdt/pojavlaunch/MainConsoleActivity.java
+++ b/app/src/main/java/net/kdt/pojavlaunch/MainConsoleActivity.java
@@ -17,6 +17,7 @@ import android.graphics.*;
import android.view.*;
import android.text.method.*;
import net.kdt.pojavlaunch.prefs.*;
+import net.kdt.pojavlaunch.value.*;
public class MainConsoleActivity extends AppCompatActivity
{
@@ -83,6 +84,13 @@ public class MainConsoleActivity extends AppCompatActivity
private void launchJava(String modPath) {
try {
+ /*
+ * 17w43a and above change Minecraf arguments from
+ * `minecraftArguments` to `arguments` so check if
+ * selected version requires LWJGL 3 or not is easy.
+ */
+ boolean isLwjgl3 = mVersionInfo.arguments != null;
+
List mJreArgs = new ArrayList();
mJreArgs.add("java");
mJreArgs.add("-Duser.home=" + Tools.MAIN_PATH);
@@ -111,6 +119,7 @@ public class MainConsoleActivity extends AppCompatActivity
* Useful if enable root mode */
process.writeToProcess("chmod -R 700 " + Tools.homeJreDir);
process.writeToProcess("cd " + Tools.MAIN_PATH);
+ process.writeToProcess("export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/minecraft_lib/lwjgl" + (isLwjgl3 ? "3" : "2"));
process.writeToProcess(mJreArgs.toArray(new String[0]));
} catch (Throwable th) {
th.printStackTrace();
diff --git a/app/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java b/app/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java
index 5c4b9f3ab..5cb7d982a 100644
--- a/app/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java
+++ b/app/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java
@@ -445,9 +445,12 @@ public class PojavLoginActivity extends MineActivity
// Extract launcher_profiles.json
// TODO: Remove after implement.
Tools.copyAssetFile(this, "launcher_profiles.json", Tools.MAIN_PATH, false);
-
Tools.copyAssetFile(this, "ClassWrapper.jar", Tools.libraries, true);
+ try {
+ Os.symlink(Tools.homeJreDir, Tools.datapath + "/xvncfiles");
+ } catch (Throwable ignored) {}
+
// Yep, the codebase from v1.0.3:
//FileAccess.copyAssetToFolderIfNonExist(this, "1.0.jar", Tools.versnDir + "/1.0");
//FileAccess.copyAssetToFolderIfNonExist(this, "1.7.3.jar", Tools.versnDir + "/1.7.3");
diff --git a/app/src/main/java/net/kdt/pojavlaunch/SimpleShellProcess.java b/app/src/main/java/net/kdt/pojavlaunch/SimpleShellProcess.java
index 147c34f76..0125946fb 100644
--- a/app/src/main/java/net/kdt/pojavlaunch/SimpleShellProcess.java
+++ b/app/src/main/java/net/kdt/pojavlaunch/SimpleShellProcess.java
@@ -20,6 +20,10 @@ public class SimpleShellProcess
); //"/system/bin/sh -c \"" + command + "\"");
}
+ public void terminate() {
+ process.destroy();
+ }
+
public void writeToProcess(String[] args) throws IOException {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < args.length; i++) {
diff --git a/app/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java b/app/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java
index 4b3c922c0..a93a435f8 100644
--- a/app/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java
+++ b/app/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java
@@ -14,11 +14,6 @@ public class LauncherPreferenceFragment extends PreferenceFragmentCompat
// Disable freeform mode in Android 6.0 or below.
findPreference("freeform").setEnabled(Build.VERSION.SDK_INT >= 24);
- SeekBarPreference seek1 = (SeekBarPreference) findPreference("maxDxRefs");
- seek1.setMin(0xFFF);
- seek1.setMax(0xFFFF);
- seek1.setValue(0xFFF);
-
SeekBarPreference seek2 = (SeekBarPreference) findPreference("timeLongPressTrigger");
seek2.setMin(100);
seek2.setMax(1000);
diff --git a/app/src/main/res/drawable-hdpi/ic_xvnc.png b/app/src/main/res/drawable-hdpi/ic_xvnc.png
new file mode 100644
index 000000000..e2a9cff64
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_xvnc.png differ
diff --git a/app/src/main/res/drawable-hdpi/mouse_icon.png b/app/src/main/res/drawable-hdpi/mouse_icon.png
new file mode 100644
index 000000000..86f362bc2
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/mouse_icon.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_xvnc.png b/app/src/main/res/drawable-ldpi/ic_xvnc.png
new file mode 100644
index 000000000..0d72d7b48
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_xvnc.png differ
diff --git a/app/src/main/res/drawable-ldpi/mouse_icon.png b/app/src/main/res/drawable-ldpi/mouse_icon.png
new file mode 100644
index 000000000..5c2278ed9
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/mouse_icon.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_xvnc.png b/app/src/main/res/drawable-mdpi/ic_xvnc.png
new file mode 100644
index 000000000..0f9bb148e
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_xvnc.png differ
diff --git a/app/src/main/res/drawable-mdpi/mouse_icon.png b/app/src/main/res/drawable-mdpi/mouse_icon.png
new file mode 100644
index 000000000..16acaa15d
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/mouse_icon.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_xvnc.png b/app/src/main/res/drawable-xhdpi/ic_xvnc.png
new file mode 100644
index 000000000..6b90f85c9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_xvnc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/mouse_icon.png b/app/src/main/res/drawable-xhdpi/mouse_icon.png
new file mode 100644
index 000000000..16acaa15d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/mouse_icon.png differ
diff --git a/app/src/main/res/layout/androidvncmain.xml b/app/src/main/res/layout/androidvncmain.xml
new file mode 100644
index 000000000..6092de3db
--- /dev/null
+++ b/app/src/main/res/layout/androidvncmain.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/canvas.xml b/app/src/main/res/layout/canvas.xml
new file mode 100644
index 000000000..ceddd7cfe
--- /dev/null
+++ b/app/src/main/res/layout/canvas.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/connection_list.xml b/app/src/main/res/layout/connection_list.xml
new file mode 100644
index 000000000..3f140ebbd
--- /dev/null
+++ b/app/src/main/res/layout/connection_list.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dummylayout.xml b/app/src/main/res/layout/dummylayout.xml
new file mode 100644
index 000000000..51e68f880
--- /dev/null
+++ b/app/src/main/res/layout/dummylayout.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/entertext.xml b/app/src/main/res/layout/entertext.xml
new file mode 100644
index 000000000..6bd5e8f96
--- /dev/null
+++ b/app/src/main/res/layout/entertext.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/importexport.xml b/app/src/main/res/layout/importexport.xml
new file mode 100644
index 000000000..02eb94247
--- /dev/null
+++ b/app/src/main/res/layout/importexport.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/intro_dialog.xml b/app/src/main/res/layout/intro_dialog.xml
new file mode 100644
index 000000000..9421448be
--- /dev/null
+++ b/app/src/main/res/layout/intro_dialog.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/metakey.xml b/app/src/main/res/layout/metakey.xml
new file mode 100644
index 000000000..efafefc83
--- /dev/null
+++ b/app/src/main/res/layout/metakey.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/repeater_dialog.xml b/app/src/main/res/layout/repeater_dialog.xml
new file mode 100644
index 000000000..da90e0901
--- /dev/null
+++ b/app/src/main/res/layout/repeater_dialog.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/xvncpromain.xml b/app/src/main/res/layout/xvncpromain.xml
new file mode 100644
index 000000000..4920a4f29
--- /dev/null
+++ b/app/src/main/res/layout/xvncpromain.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/androidvncmenu.xml b/app/src/main/res/menu/androidvncmenu.xml
new file mode 100644
index 000000000..346743445
--- /dev/null
+++ b/app/src/main/res/menu/androidvncmenu.xml
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/intro_dialog_menu.xml b/app/src/main/res/menu/intro_dialog_menu.xml
new file mode 100644
index 000000000..01bf87b11
--- /dev/null
+++ b/app/src/main/res/menu/intro_dialog_menu.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/app/src/main/res/menu/menu_runopt.xml b/app/src/main/res/menu/menu_runopt.xml
index ea675d63c..dd77a7072 100644
--- a/app/src/main/res/menu/menu_runopt.xml
+++ b/app/src/main/res/menu/menu_runopt.xml
@@ -12,4 +12,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/metakeymenu.xml b/app/src/main/res/menu/metakeymenu.xml
new file mode 100644
index 000000000..c1b153678
--- /dev/null
+++ b/app/src/main/res/menu/metakeymenu.xml
@@ -0,0 +1,4 @@
+
diff --git a/app/src/main/res/menu/xvncmenu.xml b/app/src/main/res/menu/xvncmenu.xml
new file mode 100644
index 000000000..00bd084c2
--- /dev/null
+++ b/app/src/main/res/menu/xvncmenu.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/androidvncstrings.xml b/app/src/main/res/values/androidvncstrings.xml
new file mode 100644
index 000000000..0774e4f08
--- /dev/null
+++ b/app/src/main/res/values/androidvncstrings.xml
@@ -0,0 +1,81 @@
+
+
+ Address
+ Alt
+ androidVNC
+ Down Arrow
+ Left Arrow
+ Right Arrow
+ Up Arrow
+ Auto
+ Send Text
+ Send Text/Don\'t Save It
+Mouse @
+Close
+Color Format
+Color Mode
+Connect
+Connection
+Ctrl
+Copy List
+Ctrl-Alt-Del
+Delete Connection
+Delete Selected Key
+Delete Key List
+Disconnect
+Export
+Export to file
+Enter Text to Send
+Send text
+Fit to Screen
+Force full-screen bitmap
+Import/Export Settings
+Import
+Import from file or URL
+Info
+If you need help, please check the <a href="http://code.google.com/p/android-vnc-viewer/wiki/Documentation">manual</a> (press phone\'s Menu button to access manual and other menu items), or the <a href="http://code.google.com/p/android-vnc-viewer/wiki/faq">FAQ</a>.<br>
+Welcome
+<br>--Sent text is remembered across sessions (optionally)<br>--Import/Export settings to file<br>--Button with zoom controls to bring up keyboard<br>--Can force tiling mode (very large screens)
+Input Mode
+No Pan; Trackball Mouse
+Mouse Pointer Control Mode
+Desktop Panning Mode
+Touch Pan; Trackball Mouse
+Touch Mouse; D-Pad Pan
+Touch Mouse Pan and Zoom
+TouchpadKeep
+Don\'t show again
+Keys
+List
+List Name
+Local mouse pointer
+Send Special Keys
+Mouse follow pan
+New List
+Nickname
+Off
+On
+1:1
+Manual/Wiki
+Pan Follows Mouse
+Password
+Port
+Repeater
+Enter the repeater address <b>only if</b> you are using an<br><i>UltraVNC</i>-type repeater
+Don\'t use repeater
+Repeater Information
+No repeater
+Use this repeater address
+Save as Copy
+Scaling
+Zoomable
+
+Send
+Send Key Again
+Shift
+VNC Connection
+Keyboard
+Send Keys
+Username
+For Windows Authentication
+
diff --git a/app/src/main/res/values/xvncprostrings.xml b/app/src/main/res/values/xvncprostrings.xml
new file mode 100644
index 000000000..42f569074
--- /dev/null
+++ b/app/src/main/res/values/xvncprostrings.xml
@@ -0,0 +1,67 @@
+
+
+ 4.1.3
+ 01/05/2015 4.1.3 Bug fix 01/04/2015 4.1.2 Use Activity size instead of full screen (eg: for in-screen buttons)\n29/01/2015 4.1.1 x86 fixes\n18/12/2014 4.0.2 Support for Android x86 devices\n01/11/2014 4.0.2 Improve build process\n25/10/2014 4.0.1 Improve build process\n03/10/2014 4.0.0 Added embedded androidVNC\n07/04/2014 3.9.1 Add browsable support\n06/04/2014 Geometry calculation update, more pixdepths, and Xinerama support, GPL license in source files\n02/03/2013 By default use AndroidVNC instead of PocketCloud view\n07/01/2013 Enable 7 depths for Xrender\n01/01/2013 3.5 Enable render interface and fix startup finish\n18/12/2012 3.4 New Xvnc binary with 7 depth layers support and default depth 24\n14/11/2012 3.3 Allow for remote Vnc connections\n3.2 01/10/20012 Added rgb.txt color mapping\n3.1 17/04/2012 Update buttons\n30/05/2012 3.0 Redesign of the xvnc program
+ \nXvnc Control Panel\n
+ xvncpro
+ xserver
+ Copying X Server to local storage
+ X11vnc was started
+ X11vnc
+ X/X11 Server with VNC backend
+ About
+ Help and developer info
+ Install VNC
+ Launch X
+ Stop X
+ width
+ height
+ Copy X
+ Copy Progress
+ Dummy String
+ Stop X on VNC disconnect
+ Full Screen
+ Force X params
+ keep X running
+ stop on exit
+ X11
+ Choose VNC client
+ Pocketview VNC client
+ AndroidVNC client
+ exit
+ Changelog
+ http://docs.theqvd.com/
+ Install AndroidVnc
+ Install AndroidVnc (free), if you prefer PocketView click the AndroidVnc Toggle Button
+ Error copying files:
+ Error in copy
+ Install PocketVNC
+ Install PocketViewer, if you prefer install the AndroidVnc app (free) click on the PocketView toggle button
+ Copy locally xvnc
+ Copy xvnc to local storage
+ Xvnc copy to local storage
+ Starting copy
+ Copy ended
+ Not enough space available for Xvnc
+ Install prerrequisites
+ Xvnc being copied
+ (wait checking files)
+ Connect to X server
+ X server not running
+ X11 is running
+ Xvnc error
+ Internal application error. Unknown handler key
+ Use local X
+ local
+ generic
+ Error please report: Activity was not defined
+ Allow Remote VNC
+ remote VNC disallowed
+ Remote VNC allowed
+ use XRENDER
+ disable XRENDER
+ Use XRENDER
+ use XINERAMA
+ disable XINERAMA
+ Use XINERAMA
+