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. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Des3Cipher +// @see EncryptedOutputStream +// @see EncryptedInputStream + +package android.androidVNC; + +//- import java.io.*; + + +public class DesCipher + { + + // Constructor, byte-array key. + public DesCipher( byte[] key ) + { + setKey( key ); + } + + // Key routines. + + private int[] encryptKeys = new int[32]; + private int[] decryptKeys = new int[32]; + + /// Set the key. + public void setKey( byte[] key ) + { + deskey( key, true, encryptKeys ); + deskey( key, false, decryptKeys ); + } + + // Turn an 8-byte key into internal keys. + private void deskey( byte[] keyBlock, boolean encrypting, int[] KnL ) + { + int i, j, l, m, n; + int[] pc1m = new int[56]; + int[] pcr = new int[56]; + int[] kn = new int[32]; + + for ( j = 0; j < 56; ++j ) + { + l = pc1[j]; + m = l & 07; + pc1m[j] = ( (keyBlock[l >>> 3] & bytebit[m]) != 0 )? 1: 0; + } + + for ( i = 0; i < 16; ++i ) + { + if ( encrypting ) + m = i << 1; + else + m = (15-i) << 1; + n = m+1; + kn[m] = kn[n] = 0; + for ( j = 0; j < 28; ++j ) + { + l = j+totrot[i]; + if ( l < 28 ) + pcr[j] = pc1m[l]; + else + pcr[j] = pc1m[l-28]; + } + for ( j=28; j < 56; ++j ) + { + l = j+totrot[i]; + if ( l < 56 ) + pcr[j] = pc1m[l]; + else + pcr[j] = pc1m[l-28]; + } + for ( j = 0; j < 24; ++j ) + { + if ( pcr[pc2[j]] != 0 ) + kn[m] |= bigbyte[j]; + if ( pcr[pc2[j+24]] != 0 ) + kn[n] |= bigbyte[j]; + } + } + cookey( kn, KnL ); + } + + private void cookey( int[] raw, int KnL[] ) + { + int raw0, raw1; + int rawi, KnLi; + int i; + + for ( i = 0, rawi = 0, KnLi = 0; i < 16; ++i ) + { + raw0 = raw[rawi++]; + raw1 = raw[rawi++]; + KnL[KnLi] = (raw0 & 0x00fc0000) << 6; + KnL[KnLi] |= (raw0 & 0x00000fc0) << 10; + KnL[KnLi] |= (raw1 & 0x00fc0000) >>> 10; + KnL[KnLi] |= (raw1 & 0x00000fc0) >>> 6; + ++KnLi; + KnL[KnLi] = (raw0 & 0x0003f000) << 12; + KnL[KnLi] |= (raw0 & 0x0000003f) << 16; + KnL[KnLi] |= (raw1 & 0x0003f000) >>> 4; + KnL[KnLi] |= (raw1 & 0x0000003f); + ++KnLi; + } + } + + + // Block encryption routines. + + private int[] tempInts = new int[2]; + + /// Encrypt a block of eight bytes. + public void encrypt( byte[] clearText, int clearOff, byte[] cipherText, int cipherOff ) + { + squashBytesToInts( clearText, clearOff, tempInts, 0, 2 ); + des( tempInts, tempInts, encryptKeys ); + spreadIntsToBytes( tempInts, 0, cipherText, cipherOff, 2 ); + } + + /// Decrypt a block of eight bytes. + public void decrypt( byte[] cipherText, int cipherOff, byte[] clearText, int clearOff ) + { + squashBytesToInts( cipherText, cipherOff, tempInts, 0, 2 ); + des( tempInts, tempInts, decryptKeys ); + spreadIntsToBytes( tempInts, 0, clearText, clearOff, 2 ); + } + + // Encrypt a text which is a multiple of 8 bytes. + public void encryptText(byte[] clearText, byte[] cipherText, byte[] key) + { + int i, j; + + for(i=0; i<8; i++) + { + clearText[i] ^= key[i]; + } + encrypt(clearText, 0, cipherText, 0); + for(i=8; i0; i-=8) + { + decrypt(cipherText, i, clearText, i); + for(j=0; j<8; j++) + { + clearText[i+j] ^= cipherText[i+j-8]; + } + } + /* i = 0 */ + decrypt(cipherText, 0, clearText, 0); + for(i=0; i<8; i++) + { + clearText[i] ^= key[i]; + } + } + + // The DES function. + private void des( int[] inInts, int[] outInts, int[] keys ) + { + int fval, work, right, leftt; + int round; + int keysi = 0; + + leftt = inInts[0]; + right = inInts[1]; + + work = ((leftt >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + leftt ^= (work << 4); + + work = ((leftt >>> 16) ^ right) & 0x0000ffff; + right ^= work; + leftt ^= (work << 16); + + work = ((right >>> 2) ^ leftt) & 0x33333333; + leftt ^= work; + right ^= (work << 2); + + work = ((right >>> 8) ^ leftt) & 0x00ff00ff; + leftt ^= work; + right ^= (work << 8); + right = (right << 1) | ((right >>> 31) & 1); + + work = (leftt ^ right) & 0xaaaaaaaa; + leftt ^= work; + right ^= work; + leftt = (leftt << 1) | ((leftt >>> 31) & 1); + + for ( round = 0; round < 8; ++round ) + { + work = (right << 28) | (right >>> 4); + work ^= keys[keysi++]; + fval = SP7[ work & 0x0000003f ]; + fval |= SP5[(work >>> 8) & 0x0000003f ]; + fval |= SP3[(work >>> 16) & 0x0000003f ]; + fval |= SP1[(work >>> 24) & 0x0000003f ]; + work = right ^ keys[keysi++]; + fval |= SP8[ work & 0x0000003f ]; + fval |= SP6[(work >>> 8) & 0x0000003f ]; + fval |= SP4[(work >>> 16) & 0x0000003f ]; + fval |= SP2[(work >>> 24) & 0x0000003f ]; + leftt ^= fval; + work = (leftt << 28) | (leftt >>> 4); + work ^= keys[keysi++]; + fval = SP7[ work & 0x0000003f ]; + fval |= SP5[(work >>> 8) & 0x0000003f ]; + fval |= SP3[(work >>> 16) & 0x0000003f ]; + fval |= SP1[(work >>> 24) & 0x0000003f ]; + work = leftt ^ keys[keysi++]; + fval |= SP8[ work & 0x0000003f ]; + fval |= SP6[(work >>> 8) & 0x0000003f ]; + fval |= SP4[(work >>> 16) & 0x0000003f ]; + fval |= SP2[(work >>> 24) & 0x0000003f ]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (leftt ^ right) & 0xaaaaaaaa; + leftt ^= work; + right ^= work; + leftt = (leftt << 31) | (leftt >>> 1); + work = ((leftt >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + leftt ^= (work << 8); + work = ((leftt >>> 2) ^ right) & 0x33333333; + right ^= work; + leftt ^= (work << 2); + work = ((right >>> 16) ^ leftt) & 0x0000ffff; + leftt ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ leftt) & 0x0f0f0f0f; + leftt ^= work; + right ^= (work << 4); + outInts[0] = right; + outInts[1] = leftt; + } + + + // Tables, permutations, S-boxes, etc. + + private static byte[] bytebit = { + (byte)0x01, (byte)0x02, (byte)0x04, (byte)0x08, + (byte)0x10, (byte)0x20, (byte)0x40, (byte)0x80 + }; + private static int[] bigbyte = { + 0x800000, 0x400000, 0x200000, 0x100000, + 0x080000, 0x040000, 0x020000, 0x010000, + 0x008000, 0x004000, 0x002000, 0x001000, + 0x000800, 0x000400, 0x000200, 0x000100, + 0x000080, 0x000040, 0x000020, 0x000010, + 0x000008, 0x000004, 0x000002, 0x000001 + }; + private static byte[] pc1 = { + (byte)56, (byte)48, (byte)40, (byte)32, (byte)24, (byte)16, (byte) 8, + (byte) 0, (byte)57, (byte)49, (byte)41, (byte)33, (byte)25, (byte)17, + (byte) 9, (byte) 1, (byte)58, (byte)50, (byte)42, (byte)34, (byte)26, + (byte)18, (byte)10, (byte) 2, (byte)59, (byte)51, (byte)43, (byte)35, + (byte)62, (byte)54, (byte)46, (byte)38, (byte)30, (byte)22, (byte)14, + (byte) 6, (byte)61, (byte)53, (byte)45, (byte)37, (byte)29, (byte)21, + (byte)13, (byte) 5, (byte)60, (byte)52, (byte)44, (byte)36, (byte)28, + (byte)20, (byte)12, (byte) 4, (byte)27, (byte)19, (byte)11, (byte)3 + }; + private static int[] totrot = { + 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 + }; + + private static byte[] pc2 = { + (byte)13, (byte)16, (byte)10, (byte)23, (byte) 0, (byte) 4, + (byte) 2, (byte)27, (byte)14, (byte) 5, (byte)20, (byte) 9, + (byte)22, (byte)18, (byte)11, (byte)3 , (byte)25, (byte) 7, + (byte)15, (byte) 6, (byte)26, (byte)19, (byte)12, (byte) 1, + (byte)40, (byte)51, (byte)30, (byte)36, (byte)46, (byte)54, + (byte)29, (byte)39, (byte)50, (byte)44, (byte)32, (byte)47, + (byte)43, (byte)48, (byte)38, (byte)55, (byte)33, (byte)52, + (byte)45, (byte)41, (byte)49, (byte)35, (byte)28, (byte)31, + }; + + private static int[] SP1 = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 + }; + private static int[] SP2 = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 + }; + private static int[] SP3 = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 + }; + private static int[] SP4 = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 + }; + private static int[] SP5 = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 + }; + private static int[] SP6 = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 + }; + private static int[] SP7 = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 + }; + private static int[] SP8 = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 + }; + + // Routines taken from other parts of the Acme utilities. + + /// Squash bytes down to ints. + public static void squashBytesToInts( byte[] inBytes, int inOff, int[] outInts, int outOff, int intLen ) + { + for ( int i = 0; i < intLen; ++i ) + outInts[outOff + i] = + ( ( inBytes[inOff + i * 4 ] & 0xff ) << 24 ) | + ( ( inBytes[inOff + i * 4 + 1] & 0xff ) << 16 ) | + ( ( inBytes[inOff + i * 4 + 2] & 0xff ) << 8 ) | + ( inBytes[inOff + i * 4 + 3] & 0xff ); + } + + /// Spread ints into bytes. + public static void spreadIntsToBytes( int[] inInts, int inOff, byte[] outBytes, int outOff, int intLen ) + { + for ( int i = 0; i < intLen; ++i ) + { + outBytes[outOff + i * 4 ] = (byte) ( inInts[inOff + i] >>> 24 ); + outBytes[outOff + i * 4 + 1] = (byte) ( inInts[inOff + i] >>> 16 ); + outBytes[outOff + i * 4 + 2] = (byte) ( inInts[inOff + i] >>> 8 ); + outBytes[outOff + i * 4 + 3] = (byte) inInts[inOff + i]; + } + } + } diff --git a/app/src/main/java/android/androidVNC/EnterTextDialog.java b/app/src/main/java/android/androidVNC/EnterTextDialog.java new file mode 100644 index 000000000..3b744d1eb --- /dev/null +++ b/app/src/main/java/android/androidVNC/EnterTextDialog.java @@ -0,0 +1,240 @@ +/** + * 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 java.io.*; +import java.util.*; +import net.kdt.pojavlaunch.*; + +/** + * @author Michael A. MacDonald + * + */ +class EnterTextDialog extends Dialog { + static final int NUMBER_SENT_SAVED = 100; + static final int DELETED_ID = -10; + + private VncCanvasActivity _canvasActivity; + + private EditText _textEnterText; + + private ArrayList _history; + + private int _historyIndex; + + private ImageButton _buttonNextEntry; + private ImageButton _buttonPreviousEntry; + + public EnterTextDialog(Context context) { + super(context); + setOwnerActivity((Activity)context); + _canvasActivity = (VncCanvasActivity)context; + _history = new ArrayList(); + } + + private String saveText(boolean wasSent) + { + CharSequence cs = _textEnterText.getText(); + if (cs.length()==0) + return ""; + String s = cs.toString(); + if (wasSent || _historyIndex>=_history.size() || ! s.equals(_history.get(_historyIndex).getSentText())) + { + SentTextBean added = new SentTextBean(); + added.setSentText(s); + SQLiteDatabase db = _canvasActivity.database.getWritableDatabase(); + added.Gen_insert(db); + _history.add(added); + for (int i = 0; i < _historyIndex - NUMBER_SENT_SAVED; i++) + { + SentTextBean deleteCandidate = _history.get(i); + if (deleteCandidate.get_Id() != DELETED_ID) + { + deleteCandidate.Gen_delete(db); + deleteCandidate.set_Id(DELETED_ID); + } + } + } + return s; + } + + private void sendText(String s) + { + RfbProto rfb = _canvasActivity.vncCanvas.rfb; + int l = s.length(); + for (int i = 0; ioldSize && _historyIndex==oldSize) + _historyIndex++; + if (_historyIndex < _history.size()) + { + _textEnterText.setText(_history.get(_historyIndex).getSentText()); + } + else + { + _textEnterText.setText(""); + } + } + updateButtons(); + } + + }); + _buttonPreviousEntry = (ImageButton)findViewById(R.id.buttonPreviousEntry); + _buttonPreviousEntry.setOnClickListener(new View.OnClickListener() { + + /* (non-Javadoc) + * @see android.view.View.OnClickListener#onClick(android.view.View) + */ + @Override + public void onClick(View v) { + if (_historyIndex > 0) + { + saveText(false); + _historyIndex--; + _textEnterText.setText(_history.get(_historyIndex).getSentText()); + } + updateButtons(); + } + + }); + ((Button)findViewById(R.id.buttonSendText)).setOnClickListener(new View.OnClickListener() { + + /* (non-Javadoc) + * @see android.view.View.OnClickListener#onClick(android.view.View) + */ + @Override + public void onClick(View v) { + String s = saveText(true); + sendText(s); + _textEnterText.setText(""); + _historyIndex = _history.size(); + updateButtons(); + dismiss(); + } + + }); + + ((Button)findViewById(R.id.buttonSendWithoutSaving)).setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + String s = _textEnterText.getText().toString(); + sendText(s); + _textEnterText.setText(""); + _historyIndex = _history.size(); + updateButtons(); + dismiss(); + } + }); + + ((ImageButton)findViewById(R.id.buttonTextDelete)).setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + if (_historyIndex < _history.size()) + { + String s = _textEnterText.getText().toString(); + SentTextBean bean = _history.get(_historyIndex); + if (s.equals(bean.getSentText())) + { + + bean.Gen_delete(_canvasActivity.database.getWritableDatabase()); + _history.remove(_historyIndex); + if (_historyIndex > 0) + { + _historyIndex = _historyIndex - 1; + } + } + } + String s = ""; + if (_historyIndex < _history.size()) + { + s = _history.get(_historyIndex).getSentText(); + } + _textEnterText.setText(s); + updateButtons(); + } + + }); + Cursor readInOrder = _canvasActivity.database.getReadableDatabase().rawQuery( + "select * from " + SentTextBean.GEN_TABLE_NAME + " ORDER BY _id", null); + try + { + SentTextBean.Gen_populateFromCursor(readInOrder, _history, SentTextBean.GEN_NEW); + } + finally + { + readInOrder.close(); + } + _historyIndex = _history.size(); + + updateButtons(); + } + + private void updateButtons() + { + _buttonPreviousEntry.setEnabled(_historyIndex > 0); + _buttonNextEntry.setEnabled(_historyIndex <_history.size()); + } + + /* (non-Javadoc) + * @see android.app.Dialog#onStart() + */ + @Override + protected void onStart() { + super.onStart(); + _textEnterText.requestFocus(); + } +} diff --git a/app/src/main/java/android/androidVNC/FitToScreenScaling.java b/app/src/main/java/android/androidVNC/FitToScreenScaling.java new file mode 100644 index 000000000..f5c0e21b9 --- /dev/null +++ b/app/src/main/java/android/androidVNC/FitToScreenScaling.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 FitToScreenScaling extends AbstractScaling { + + /** + * @param id + * @param scaleType + */ + FitToScreenScaling() { + super(R.id.itemFitToScreen, ScaleType.FIT_CENTER); + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractScaling#isAbleToPan() + */ + @Override + boolean isAbleToPan() { + return false; + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractScaling#isValidInputMode(int) + */ + @Override + boolean isValidInputMode(int mode) { + return mode == R.id.itemInputFitToScreen; + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractScaling#getDefaultHandlerId() + */ + @Override + int getDefaultHandlerId() { + return R.id.itemInputFitToScreen; + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractScaling#setCanvasScaleType(android.androidVNC.VncCanvas) + */ + @Override + void setScaleTypeForActivity(VncCanvasActivity activity) { + super.setScaleTypeForActivity(activity); + activity.vncCanvas.absoluteXPosition = activity.vncCanvas.absoluteYPosition = 0; + activity.vncCanvas.scrollTo(0, 0); + } + +} diff --git a/app/src/main/java/android/androidVNC/FullBufferBitmapData.java b/app/src/main/java/android/androidVNC/FullBufferBitmapData.java new file mode 100644 index 000000000..82f59ed7e --- /dev/null +++ b/app/src/main/java/android/androidVNC/FullBufferBitmapData.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2010 Michael A. MacDonald + */ +package android.androidVNC; + +import java.io.IOException; +import java.util.Arrays; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.widget.ImageView; + +/** + * @author Michael A. MacDonald + * + */ +class FullBufferBitmapData extends AbstractBitmapData { + + int xoffset; + int yoffset; + + /** + * @author Michael A. MacDonald + * + */ + class Drawable extends AbstractBitmapDrawable { + + /** + * @param data + */ + public Drawable(AbstractBitmapData data) { + super(data); + // TODO Auto-generated constructor stub + } + + /* (non-Javadoc) + * @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas) + */ + @Override + public void draw(Canvas canvas) { + if (vncCanvas.getScaleType() == ImageView.ScaleType.FIT_CENTER) + { + canvas.drawBitmap(data.bitmapPixels, 0, data.framebufferwidth, xoffset, yoffset, framebufferwidth, framebufferheight, false, null); + } + else + { + float scale = vncCanvas.getScale(); + int xo = xoffset < 0 ? 0 : xoffset; + int yo = yoffset < 0 ? 0 : yoffset; + /* + if (scale == 1 || scale <= 0) + { + */ + int drawWidth = vncCanvas.getVisibleWidth(); + if (drawWidth + xo > data.framebufferwidth) + drawWidth = data.framebufferwidth - xo; + int drawHeight = vncCanvas.getVisibleHeight(); + if (drawHeight + yo > data.framebufferheight) + drawHeight = data.framebufferheight - yo; + canvas.drawBitmap(data.bitmapPixels, offset(xo, yo), data.framebufferwidth, xo, yo, drawWidth, drawHeight, false, null); + /* + } + else + { + int scalewidth = (int)(vncCanvas.getVisibleWidth() / scale + 1); + if (scalewidth + xo > data.framebufferwidth) + scalewidth = data.framebufferwidth - xo; + int scaleheight = (int)(vncCanvas.getVisibleHeight() / scale + 1); + if (scaleheight + yo > data.framebufferheight) + scaleheight = data.framebufferheight - yo; + canvas.drawBitmap(data.bitmapPixels, offset(xo, yo), data.framebufferwidth, xo, yo, scalewidth, scaleheight, false, null); + } + */ + } + if(data.vncCanvas.connection.getUseLocalCursor()) + { + setCursorRect(data.vncCanvas.mouseX, data.vncCanvas.mouseY); + clipRect.set(cursorRect); + if (canvas.clipRect(cursorRect)) + { + drawCursor(canvas); + } + } + } + } + + /** + * Multiply this times total number of pixels to get estimate of process size with all buffers plus + * safety factor + */ + static final int CAPACITY_MULTIPLIER = 7; + + /** + * @param p + * @param c + */ + public FullBufferBitmapData(RfbProto p, VncCanvas c, int capacity) { + super(p, c); + framebufferwidth=rfb.framebufferWidth; + framebufferheight=rfb.framebufferHeight; + bitmapwidth=framebufferwidth; + bitmapheight=framebufferheight; + android.util.Log.i("FBBM", "bitmapsize = ("+bitmapwidth+","+bitmapheight+")"); + bitmapPixels = new int[framebufferwidth * framebufferheight]; + } + + /* (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#createDrawable() + */ + @Override + AbstractBitmapDrawable createDrawable() { + return new Drawable(this); + } + + /* (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) { + int color = paint.getColor(); + int offset = offset(x,y); + if (w > 10) + { + for (int j = 0; j < h; j++, offset += framebufferwidth) + { + Arrays.fill(bitmapPixels, offset, offset + w, color); + } + } + else + { + for (int j = 0; j < h; j++, offset += framebufferwidth - w) + { + for (int k = 0; k < w; k++, offset++) + { + bitmapPixels[offset] = color; + } + } + } + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractBitmapData#offset(int, int) + */ + @Override + int offset(int x, int y) { + return x + y * framebufferwidth; + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractBitmapData#scrollChanged(int, int) + */ + @Override + void scrollChanged(int newx, int newy) { + xoffset = newx; + yoffset = newy; + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractBitmapData#syncScroll() + */ + @Override + void syncScroll() { + // Don't need to do anything here + + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractBitmapData#updateBitmap(int, int, int, int) + */ + @Override + void updateBitmap(int x, int y, int w, int h) { + // Don't need to do anything here + + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractBitmapData#validDraw(int, int, int, int) + */ + @Override + boolean validDraw(int x, int y, int w, int h) { + return true; + } + + /* (non-Javadoc) + * @see android.androidVNC.AbstractBitmapData#writeFullUpdateRequest(boolean) + */ + @Override + void writeFullUpdateRequest(boolean incremental) throws IOException { + rfb.writeFramebufferUpdateRequest(0, 0, framebufferwidth, framebufferheight, incremental); + } + +} diff --git a/app/src/main/java/android/androidVNC/IConnectionBean.java b/app/src/main/java/android/androidVNC/IConnectionBean.java new file mode 100644 index 000000000..b5c2f5b80 --- /dev/null +++ b/app/src/main/java/android/androidVNC/IConnectionBean.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2009 Michael A. MacDonald + */ +package android.androidVNC; + +import com.antlersoft.android.db.*; + +/** + * @author Michael A. MacDonald + * + */ +@TableInterface(ImplementingClassName="AbstractConnectionBean",TableName="CONNECTION_BEAN") +interface IConnectionBean { + @FieldAccessor + long get_Id(); + @FieldAccessor + String getNickname(); + @FieldAccessor + String getAddress(); + @FieldAccessor + int getPort(); + @FieldAccessor + String getPassword(); + @FieldAccessor + String getColorModel(); + /** + * Records bitmap data implementation selection. 0 for auto, 1 for force full bitmap, 2 for force tiled + *

+ * 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: + *

+ */ +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: + * + */ +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 @@ + + + + + + + + + + + + + +