mirror of
https://github.com/xoureldeen/Vectras-VM-Android.git
synced 2026-07-09 17:28:54 +00:00
2337 lines
66 KiB
Java
2337 lines
66 KiB
Java
//
|
||
// 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 android.app.Activity;
|
||
import android.app.ProgressDialog;
|
||
import android.content.Context;
|
||
import android.content.DialogInterface;
|
||
import android.content.Intent;
|
||
import android.graphics.Color;
|
||
import android.graphics.Paint;
|
||
import android.graphics.Paint.Style;
|
||
import android.graphics.Point;
|
||
import android.graphics.Rect;
|
||
import android.os.Build;
|
||
import android.os.Handler;
|
||
import android.util.AttributeSet;
|
||
import android.util.Log;
|
||
import android.view.Display;
|
||
import android.view.InputDevice;
|
||
import android.view.KeyEvent;
|
||
import android.view.MotionEvent;
|
||
import android.view.View;
|
||
|
||
import com.antlersoft.android.bc.BCFactory;
|
||
import com.vectras.qemu.Config;
|
||
import com.vectras.vm.R;
|
||
import com.vectras.vm.utils.DeviceUtils;
|
||
import com.vectras.vm.utils.UIUtils;
|
||
|
||
import java.io.IOException;
|
||
import java.util.Objects;
|
||
import java.util.concurrent.Executors;
|
||
import java.util.concurrent.ScheduledExecutorService;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.concurrent.atomic.AtomicLong;
|
||
import java.util.zip.Inflater;
|
||
|
||
import androidx.appcompat.widget.AppCompatImageView;
|
||
|
||
public class VncCanvas extends AppCompatImageView {
|
||
|
||
private final static String TAG = "VncCanvas";
|
||
private final static boolean LOCAL_LOGV = true;
|
||
public AbstractScaling scaling;
|
||
// Available to activity
|
||
public 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;
|
||
public 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 <i>visible</i> part of the
|
||
* screen, in full-frame coordinates
|
||
*/
|
||
int absoluteXPosition = 0, absoluteYPosition = 0;
|
||
public boolean mouseDown;
|
||
|
||
/**
|
||
* 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);
|
||
}
|
||
|
||
public VncCanvas(Context context) {
|
||
super(context);
|
||
}
|
||
|
||
public static int retries;
|
||
public static int MAX_RETRIES = 5;
|
||
|
||
/**
|
||
* 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());
|
||
|
||
if (Build.VERSION.SDK_INT >= 28) {
|
||
this.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
|
||
}
|
||
|
||
setOnGenericMotionListener(new VNCGenericMotionListener_API12());
|
||
setOnTouchListener(new VNCOnTouchListener());
|
||
|
||
// Startup the RFB thread with a nifty progess dialog
|
||
final ProgressDialog pd = new ProgressDialog(getContext(), R.style.MainDialogTheme);
|
||
pd.setTitle("Connecting to VM Console");
|
||
pd.setMessage("Please wait...");
|
||
pd.setIndeterminate(true);
|
||
pd.setCancelable(true);
|
||
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||
@Override
|
||
public void onCancel(DialogInterface dialog) {
|
||
closeConnection();
|
||
handler.post(new Runnable() {
|
||
public void run() {
|
||
Utils.showErrorMessage(getContext(), "VNC connection aborted!");
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// Show the ProgressDialog
|
||
// Did not give show to not flash.
|
||
//pd.show();
|
||
final Display display = Objects.requireNonNull(pd.getWindow()).getWindowManager().getDefaultDisplay();
|
||
Thread t = new Thread() {
|
||
|
||
public void run() {
|
||
try {
|
||
int width;
|
||
int height;
|
||
Point size = new Point();
|
||
display.getSize(size);
|
||
width = size.x;
|
||
height = size.y;
|
||
|
||
connectAndAuthenticate(connection.getUserName(), connection.getPassword());
|
||
doProtocolInitialisation(width, height);
|
||
handler.post(() -> {
|
||
// pd.setMessage("Downloading first frame.\nPlease
|
||
// wait...");
|
||
});
|
||
processNormalProtocol(getContext(), pd, setModes);
|
||
} catch (Throwable e) {
|
||
if (maintainConnection) {
|
||
Log.e(TAG, "initializeVncCanvas: ", e);
|
||
// 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 if (e instanceof ArrayIndexOutOfBoundsException || e instanceof IOException) {
|
||
// Retry
|
||
if (retries < MAX_RETRIES) {
|
||
retries++;
|
||
reload();
|
||
}
|
||
} else {
|
||
String error = "VNC connection failed!";
|
||
if (e.getMessage() != null && (e.getMessage().contains("authentication"))) {
|
||
error = "VNC authentication failed!";
|
||
}
|
||
final String error_ = error + "<br>" + e.getLocalizedMessage();
|
||
handler.post(() -> Utils.showFatalErrorMessage(getContext(), error_));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
t.start();
|
||
|
||
setupScaleMode();
|
||
}
|
||
|
||
public void reload() {
|
||
Log.d(TAG, "Reconnecting...");
|
||
Activity activity = ((Activity) getContext());
|
||
Intent data = new Intent();
|
||
activity.setResult(Config.VNC_RESET_RESULT_CODE, data);
|
||
// Do not terminate Activity when no signal.
|
||
// activity.finish();
|
||
|
||
}
|
||
|
||
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");
|
||
|
||
}
|
||
|
||
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().isEmpty()) {
|
||
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);
|
||
|
||
initBitmapData(dx, dy);
|
||
|
||
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;
|
||
}
|
||
|
||
void initBitmapData(int dx, int dy) {
|
||
int capacity = BCFactory.getInstance().getBCActivityManager()
|
||
.getMemoryClass(Utils.getActivityManager(getContext()));
|
||
|
||
boolean useFull = false;
|
||
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 (bitmapData != null) bitmapData.dispose();
|
||
|
||
if (!useFull) {
|
||
bitmapData = new LargeBitmapData(rfb, this, dx, dy, capacity);
|
||
} else {
|
||
bitmapData = new FullBufferBitmapData(rfb, this, capacity);
|
||
}
|
||
}
|
||
|
||
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 (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);
|
||
connected();
|
||
//
|
||
// 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");
|
||
|
||
Point size = new Point();
|
||
VncCanvasActivity.display.getSize(size);
|
||
initBitmapData(size.x, size.y);
|
||
setupScaleMode();
|
||
handler.post(this::reload);
|
||
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();
|
||
}
|
||
}
|
||
|
||
setEncodings(true);
|
||
|
||
if (isNeedWriteFullUpdateRequest) {
|
||
bitmapData.writeFullUpdateRequest(false);
|
||
isNeedWriteFullUpdateRequest = false;
|
||
} else {
|
||
boolean fullUpdateNeeded = false;
|
||
|
||
if (pendingColorModel != null) {
|
||
setPixelFormat();
|
||
fullUpdateNeeded = true;
|
||
}
|
||
|
||
bitmapData.writeFullUpdateRequest(!fullUpdateNeeded);
|
||
}
|
||
|
||
break;
|
||
|
||
case RfbProto.SetColourMapEntries:
|
||
throw new Exception("Can't handle SetColourMapEntries message");
|
||
|
||
case RfbProto.Bell:
|
||
handler.post(() -> UIUtils.toastShort(context, "VNC Beep"));
|
||
break;
|
||
|
||
case RfbProto.ServerCutText:
|
||
String s = rfb.readServerCutText();
|
||
if (s != null && !s.isEmpty()) {
|
||
Log.e(TAG, "processNormalProtocol: " + s);
|
||
}
|
||
break;
|
||
|
||
case RfbProto.TextChat:
|
||
// UltraVNC extension
|
||
String msg = rfb.readTextChatMsg();
|
||
if (msg != null && !msg.isEmpty()) {
|
||
Log.e(TAG, "processNormalProtocol: " + msg);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
throw new Exception("Unknown RFB message type " + msgType);
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
throw e;
|
||
} finally {
|
||
Log.v(TAG, "Closing VNC Connection");
|
||
rfb.close();
|
||
disconnected();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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 (rfb != null) {
|
||
rfb.close();
|
||
}
|
||
|
||
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();
|
||
try {
|
||
scrollTo((int) ((absoluteXPosition + ((float) getWidth() - getImageWidth()) / 2) * scale),
|
||
(int) ((absoluteYPosition + ((float) getHeight() - getImageHeight()) / 2) * scale));
|
||
} catch (Exception e) {
|
||
Log.v("VNC", "Error: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
|
||
Point outSize = new Point();
|
||
int height = 0;
|
||
int width = 0;
|
||
VncCanvasActivity.display.getSize(outSize);
|
||
height = outSize.y;
|
||
width = outSize.x;
|
||
|
||
if (absoluteXPosition + getVisibleWidth() + sX > getImageWidth()) {
|
||
sX = getImageWidth() - getVisibleWidth() - absoluteXPosition;
|
||
}
|
||
if (absoluteYPosition + getVisibleHeight() + sY > getImageHeight() + height * .6) {
|
||
sY = getImageHeight() + height * .6 - getVisibleHeight() - absoluteYPosition;
|
||
}
|
||
|
||
absoluteXPosition += (int) sX;
|
||
absoluteYPosition += (int) 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 |
|
||
0xFF000000
|
||
|(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 final 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.isEmpty()) {
|
||
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;
|
||
private boolean ALT_PRESSED = false;
|
||
private boolean CTRL_PRESSED = false;
|
||
|
||
/**
|
||
* 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) {
|
||
boolean useMiddleButton = false;
|
||
|
||
if(evt.getButtonState() == MotionEvent.BUTTON_SECONDARY){
|
||
useRightButton = true;
|
||
} else if(evt.getButtonState() == MotionEvent.BUTTON_TERTIARY){
|
||
useMiddleButton = true;
|
||
}
|
||
|
||
//XXX: not reliable with laptop trackpads
|
||
// if(Config.mouseMode == Config.MouseMode.External
|
||
// && MotionEvent.TOOL_TYPE_FINGER == evt.getToolType(0))
|
||
// return true;
|
||
//
|
||
// if(Config.mouseMode == Config.MouseMode.Trackpad
|
||
// && MotionEvent.TOOL_TYPE_MOUSE == evt.getToolType(0))
|
||
// return true;
|
||
|
||
return processPointerEvent((int) evt.getX(), (int) evt.getY(), evt.getAction(), evt.getMetaState(), downEvent,
|
||
useRightButton, useMiddleButton, false);
|
||
}
|
||
|
||
boolean processPointerEvent(int x, int y, int action,
|
||
int modifiers, boolean mouseIsDown, boolean useRightButton,
|
||
boolean useMiddleButton, boolean scrollUp) {
|
||
//Log.v("Vectras", "processPointerEvent: " + x + ", " + y + ", "
|
||
// + action + ", " + modifiers + ", " + mouseIsDown + ", "
|
||
// + useRightButton + ", " + useMiddleButton + ", " + scrollUp
|
||
//);
|
||
|
||
if (rfb != null && rfb.inNormalProtocol) {
|
||
if (action == MotionEvent.ACTION_DOWN || (mouseIsDown && action == MotionEvent.ACTION_MOVE)) {
|
||
if (useRightButton) {
|
||
// Log.v("Vectras", "Right Button Down");
|
||
pointerMask |= MOUSE_BUTTON_RIGHT;
|
||
} else if (useMiddleButton) {
|
||
pointerMask |= MOUSE_BUTTON_MIDDLE;
|
||
}else {
|
||
//Log.v("Vectras", "Left Button Down: x=" + x + ", y=" + y);
|
||
pointerMask |= MOUSE_BUTTON_LEFT;
|
||
}
|
||
} else if (action == MotionEvent.ACTION_SCROLL) {
|
||
// Log.v("Vectras", "Button Up");
|
||
if(scrollUp)
|
||
pointerMask |= MOUSE_BUTTON_SCROLL_UP;
|
||
else
|
||
pointerMask |= MOUSE_BUTTON_SCROLL_DOWN;
|
||
} else if (action == MotionEvent.ACTION_UP) {
|
||
// Log.v("Vectras", "Button Up");
|
||
//pointerMask = 0;
|
||
if (useRightButton) {
|
||
// Log.v("Vectras", "Right Button Down");
|
||
pointerMask &= ~MOUSE_BUTTON_RIGHT;
|
||
} else if (useMiddleButton) {
|
||
pointerMask &= ~MOUSE_BUTTON_MIDDLE;
|
||
}else {
|
||
//Log.v("Vectras", "Left Button Down: x=" + x + ", y=" + y);
|
||
//XXX: Mouse middle click cannot always be detected so we
|
||
// reset all buttons (left, middle, click) to be safe
|
||
pointerMask = 0;
|
||
}
|
||
}
|
||
|
||
if (bitmapData != null) 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;
|
||
}
|
||
if (bitmapData != null) bitmapData.invalidateMousePosition();
|
||
try {
|
||
rfb.writePointerEvent(mouseX, mouseY, modifiers, pointerMask);
|
||
if (action == MotionEvent.ACTION_SCROLL) {
|
||
rfb.writePointerEvent(mouseX, mouseY, 0, 0);
|
||
pointerMask = 0;
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "processPointerEvent: ", e);
|
||
}
|
||
panToMouse();
|
||
|
||
if (getScaleX() > scalingX) {
|
||
if (getScaleType() == STRETCH_TO_FIT_MODE) {
|
||
float scaleX = getScaleX();
|
||
float scaleY = getScaleY();
|
||
|
||
float centerX = getWidth() / 2f;
|
||
float centerY = getHeight() / 2f;
|
||
|
||
float tx = centerX - (mouseX * (scaleX / (scaleX / scalingX)));
|
||
float ty = centerY - (mouseY * (scaleY / (scaleY / scalingY)));
|
||
|
||
setTranslationX(tx);
|
||
setTranslationY(ty);
|
||
} else {
|
||
setPivotX(0);
|
||
setPivotY(0);
|
||
|
||
float scaleX = getScaleX();
|
||
float scaleY = getScaleY();
|
||
float centerX = getWidth() / 2f;
|
||
float centerY = getHeight() / 2f;
|
||
|
||
float imageWidth = getImageWidth();
|
||
float imageHeight = getImageHeight();
|
||
|
||
float fitScale = Math.min((float) getWidth() / imageWidth, (float) getHeight() / imageHeight);
|
||
|
||
float offsetX = (getWidth() - (imageWidth * fitScale)) / 2f;
|
||
float offsetY = (getHeight() - (imageHeight * fitScale)) / 2f;
|
||
|
||
float mouseOnScreenX = (mouseX * fitScale + offsetX);
|
||
float mouseOnScreenY = (mouseY * fitScale + offsetY);
|
||
|
||
setTranslationX(centerX - mouseOnScreenX * scaleX);
|
||
setTranslationY(centerY - mouseOnScreenY * scaleY);
|
||
}
|
||
} else {
|
||
setTranslationX(0);
|
||
setTranslationY(0);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private int isSpecialKey(int key) {
|
||
return switch (key) {
|
||
case '!' -> '1';
|
||
case '@' -> '2';
|
||
case '#' -> '3';
|
||
case '$' -> '4';
|
||
case '%' -> '5';
|
||
case '^' -> '6';
|
||
case '&' -> '7';
|
||
case '*' -> '8';
|
||
case '(' -> '9';
|
||
case ')' -> '0';
|
||
case '_' -> '-';
|
||
case '+' -> '=';
|
||
case '~' -> '`';
|
||
case '{' -> '[';
|
||
case '}' -> ']';
|
||
case '|' -> '\\';
|
||
case '\"' -> '\'';
|
||
case ':' -> ';';
|
||
case '<' -> ',';
|
||
case '>' -> '.';
|
||
case '?' -> '/';
|
||
default -> -1;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 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 e) {
|
||
Log.e(TAG, "MouseScrollRunnable: ", e);
|
||
}
|
||
}
|
||
}
|
||
|
||
public synchronized 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) {
|
||
Log.e(TAG, "processLocalKeyEvent: ", ioe);
|
||
}
|
||
return true;
|
||
}
|
||
if (rfb != null && rfb.inNormalProtocol) {
|
||
boolean down = (evt.getAction() == KeyEvent.ACTION_DOWN);
|
||
int key;
|
||
int metaState = evt.getMetaState();
|
||
metaState = 0;
|
||
// Log.v("Key Pressed", keyCode + ", metaState = " + metaState);
|
||
|
||
switch (keyCode) {
|
||
case KeyEvent.KEYCODE_BACK:
|
||
// key = 0xff1b;
|
||
return false;
|
||
// 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_FORWARD_DEL:
|
||
key = Objects.requireNonNull(MetaKeyBean.keysByKeyCode.get(KeyEvent.KEYCODE_DEL)).keySym;
|
||
break;
|
||
case KeyEvent.KEYCODE_ALT_LEFT:
|
||
case KeyEvent.KEYCODE_ALT_RIGHT:
|
||
this.ALT_PRESSED = true;
|
||
return true;
|
||
case KeyEvent.KEYCODE_MOVE_HOME:
|
||
key = 0xFF50;
|
||
break;
|
||
case KeyEvent.KEYCODE_INSERT:
|
||
key = 0xFF63;
|
||
break;
|
||
case KeyEvent.KEYCODE_MOVE_END:
|
||
key = 0xFF57;
|
||
break;
|
||
case KeyEvent.KEYCODE_PAGE_DOWN:
|
||
key = 0xFF56;
|
||
break;
|
||
case KeyEvent.KEYCODE_PAGE_UP:
|
||
key = 0xFF55;
|
||
break;
|
||
case KeyEvent.KEYCODE_ENTER:
|
||
key = 0xff0d;
|
||
break;
|
||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||
key = 0xff0d;
|
||
break;
|
||
case KeyEvent.KEYCODE_TAB:
|
||
key = 0xFF09;
|
||
break;
|
||
case 111: // ESCAPE
|
||
key = 0xff1b;
|
||
break;
|
||
default:
|
||
key = evt.getUnicodeChar();
|
||
//Log.v("unicode", "Unicode Char for " + evt.getKeyCode() + " is " + key);
|
||
|
||
//ΧΧΧ: Workaround for some chars not recognized by QEMU
|
||
int specialKey = isSpecialKey(key);
|
||
if (specialKey != -1) {
|
||
key = specialKey;
|
||
metaState = metaState | VncCanvas.SHIFT_MASK;
|
||
} else if (keyCode >= 131 && keyCode <= 142) {
|
||
// Function Key pressed
|
||
key = 0xFFBE + keyCode - 131;
|
||
} else if (key == 0){
|
||
//Key is a meta combination or unknown
|
||
key = evt.getUnicodeChar(0);
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
if ((evt.getMetaState() & KeyEvent.META_CTRL_ON) == KeyEvent.META_CTRL_ON) {
|
||
// Log.v("meta", "setting ctrl mask");
|
||
metaState = metaState | VncCanvas.CTRL_MASK;
|
||
}
|
||
|
||
if ((evt.getMetaState() & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON || this.ALT_PRESSED) {
|
||
// Log.v("meta", "setting alt mask");
|
||
metaState = metaState | VncCanvas.ALT_MASK;
|
||
}
|
||
|
||
if ((evt.getMetaState() & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON) {
|
||
// Log.v("meta", "setting shift mask");
|
||
metaState = metaState | VncCanvas.SHIFT_MASK;
|
||
}
|
||
try {
|
||
if (afterMenu) {
|
||
afterMenu = false;
|
||
if (!down && key != lastKeyDown) {
|
||
return true;
|
||
}
|
||
}
|
||
if (down) {
|
||
lastKeyDown = key;
|
||
}
|
||
|
||
rfb.writeKeyEvent(key, metaState, down);
|
||
this.ALT_PRESSED = false;
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "processLocalKeyEvent: ", e);
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public void closeConnection() {
|
||
maintainConnection = false;
|
||
if (rfb != null) {
|
||
rfb.close();
|
||
}
|
||
}
|
||
|
||
public void sendMetaKey1(int key, int flags) {
|
||
try {
|
||
rfb.writeKeyEvent(key, flags, true);
|
||
rfb.writeKeyEvent(key, flags, false);
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendMetaKey1: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendMetaKey1Down(int key, int flags) {
|
||
try {
|
||
rfb.writeKeyEvent(key, flags, true);
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendMetaKey1Down: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendMetaKey1Up(int key, int flags) {
|
||
try {
|
||
rfb.writeKeyEvent(key, flags, false);
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendMetaKey1Up: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendAKey(int _key) {
|
||
try {
|
||
rfb.writeAkey(_key);
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendAKey: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendCtrlAltDel() {
|
||
try {
|
||
rfb.writeCtrlAltDel();
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendCtrlAltDel: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendCtrlC() {
|
||
try {
|
||
rfb.writeCtrlC();
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendCtrlC: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendCtrlX() {
|
||
try {
|
||
rfb.writeCtrlX();
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendCtrlX: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendCtrlV() {
|
||
try {
|
||
rfb.writeCtrlV();
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendCtrlV: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendCtrlA() {
|
||
try {
|
||
rfb.writeCtrlA();
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendCtrlA: ", ioe);
|
||
}
|
||
|
||
}
|
||
|
||
public void sendText(String s) {
|
||
int l = s.length();
|
||
for (int i = 0; i < l; i++) {
|
||
char c = s.charAt(i);
|
||
int meta = 0;
|
||
int keysym = c;
|
||
if (Character.isISOControl(c)) {
|
||
if (c == '\n') {
|
||
keysym = Objects.requireNonNull(MetaKeyBean.keysByKeyCode.get(KeyEvent.KEYCODE_ENTER)).keySym;
|
||
} else {
|
||
continue;
|
||
}
|
||
}
|
||
try {
|
||
rfb.writeKeyEvent(keysym, meta, true);
|
||
rfb.writeKeyEvent(keysym, meta, false);
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendText: ",ioe);
|
||
}
|
||
}
|
||
}
|
||
|
||
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) {
|
||
Log.e(TAG, "sendMetaKey: ",ioe);
|
||
}
|
||
} else {
|
||
try {
|
||
rfb.writeKeyEvent(meta.getKeySym(), meta.getMetaFlags(), true);
|
||
rfb.writeKeyEvent(meta.getKeySym(), meta.getMetaFlags(), false);
|
||
} catch (IOException ioe) {
|
||
Log.e(TAG, "sendMetaKey: ",ioe);
|
||
}
|
||
}
|
||
}
|
||
|
||
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() {
|
||
if (bitmapData == null) {
|
||
return 0;
|
||
}
|
||
return bitmapData.framebufferwidth;
|
||
}
|
||
|
||
public int getImageHeight() {
|
||
if (bitmapData == null) {
|
||
return 0;
|
||
}
|
||
return bitmapData.framebufferheight;
|
||
}
|
||
|
||
public int getCenteredXOffset() {
|
||
if (bitmapData == null) {
|
||
return 0;
|
||
}
|
||
return (bitmapData.framebufferwidth - getWidth()) / 2;
|
||
}
|
||
|
||
public int getCenteredYOffset() {
|
||
if (bitmapData == null) {
|
||
return 0;
|
||
}
|
||
return (bitmapData.framebufferheight - getHeight()) / 2;
|
||
}
|
||
|
||
/**
|
||
* 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) {
|
||
Log.e(TAG, "setEncodings: ", e);
|
||
}
|
||
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] = 0xFF000000
|
||
|(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 = 0xFF000000 | (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;
|
||
}
|
||
|
||
totalBytesReceived.addAndGet(count);
|
||
} 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] = 0xFF000000
|
||
| ((readPixelsBuffer[idx + 2] & 0xFF) << 16 | (readPixelsBuffer[idx + 1] & 0xFF) << 8
|
||
| (readPixelsBuffer[idx] & 0xFF));
|
||
}
|
||
|
||
totalBytesReceived.addAndGet(l);
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
boolean isNeedWriteFullUpdateRequest = true;
|
||
|
||
public void connected() {
|
||
// Request initial framebuffer update
|
||
isNeedWriteFullUpdateRequest = true;
|
||
|
||
startDeadCheck();
|
||
|
||
VncCanvasActivity activity = (VncCanvasActivity) VncCanvas.this.getContext();
|
||
activity.onConnected();
|
||
}
|
||
|
||
public void disconnected() {
|
||
stopDeadCheck();
|
||
|
||
VncCanvasActivity activity = (VncCanvasActivity) VncCanvas.this.getContext();
|
||
activity.onDisconnected();
|
||
}
|
||
|
||
private final AtomicLong totalBytesReceived = new AtomicLong(0);
|
||
private long lastCheckedBytes = 0;
|
||
ScheduledExecutorService deadCheckScheduler;
|
||
|
||
boolean isNeedDeadCheck = DeviceUtils.isColorOS(getContext());
|
||
|
||
private void startDeadCheck() {
|
||
if (!isNeedDeadCheck) return;
|
||
|
||
totalBytesReceived.set(0);
|
||
lastCheckedBytes = totalBytesReceived.get();
|
||
deadCheckScheduler = Executors.newSingleThreadScheduledExecutor();
|
||
|
||
deadCheckScheduler.scheduleWithFixedDelay(() -> {
|
||
long currentBytes = totalBytesReceived.get();
|
||
|
||
if (currentBytes == lastCheckedBytes) {
|
||
isNeedWriteFullUpdateRequest = true;
|
||
Log.d(TAG, "No response was detected and a WriteFullUpdateRequest was sent.");
|
||
} else {
|
||
lastCheckedBytes = currentBytes;
|
||
}
|
||
}, 5000, 5000, TimeUnit.SECONDS);
|
||
}
|
||
|
||
private void stopDeadCheck() {
|
||
if (!isNeedDeadCheck) return;
|
||
|
||
if (deadCheckScheduler != null) {
|
||
deadCheckScheduler.shutdownNow();
|
||
deadCheckScheduler = null;
|
||
}
|
||
}
|
||
|
||
public float getFramebufferWidth() {
|
||
return bitmapData.framebufferwidth;
|
||
}
|
||
|
||
public float getFramebufferHeight() {
|
||
return bitmapData.framebufferheight;
|
||
}
|
||
|
||
class VNCOnTouchListener implements View.OnTouchListener {
|
||
|
||
@Override
|
||
public boolean onTouch(View v, MotionEvent event) {
|
||
// TODO Auto-generated method stub
|
||
//Log.i("VNCOnTouchListener", "onTouch");
|
||
if(Config.mouseMode == Config.MouseMode.Trackpad) {
|
||
return false;
|
||
}
|
||
return processPointerEvent(event, event.getAction() == MotionEvent.ACTION_DOWN);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
class VNCGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||
private VncCanvas mSurface;
|
||
|
||
// Generic Motion (mouse hover, joystick...) events go here
|
||
@Override
|
||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||
float x, y;
|
||
int action;
|
||
|
||
switch (event.getSource()) {
|
||
case InputDevice.SOURCE_JOYSTICK:
|
||
case InputDevice.SOURCE_GAMEPAD:
|
||
case InputDevice.SOURCE_DPAD:
|
||
return true;
|
||
case InputDevice.SOURCE_MOUSE:
|
||
if(Config.mouseMode == Config.MouseMode.Trackpad)
|
||
break;
|
||
|
||
action = event.getActionMasked();
|
||
//Log.d("SDL", "onGenericMotion, action = " + action + "," + event.getX() + ", " + event.getY());
|
||
switch (action) {
|
||
case MotionEvent.ACTION_SCROLL:
|
||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||
//Log.d("SDL", "Mouse Scroll: " +event.getX() + ":" + event.getY() + " => " + x + "," + y);
|
||
|
||
//TODO:
|
||
//SDLActivity.onSDLNativeMouse(0, action, x, y);
|
||
//processPointerEvent(event,false);
|
||
|
||
// Log.v("Vectras", "Button Up");
|
||
boolean scrollUp=false;
|
||
if (y > 0)
|
||
scrollUp = true;
|
||
else if (y < 0)
|
||
scrollUp = false;
|
||
|
||
return processPointerEvent((int) event.getX(), (int) event.getY(), event.getAction(), event.getMetaState(), false,
|
||
false, false, scrollUp);
|
||
//return true;
|
||
|
||
case MotionEvent.ACTION_HOVER_MOVE:
|
||
if(Config.processMouseHistoricalEvents) {
|
||
final int historySize = event.getHistorySize();
|
||
for (int h = 0; h < historySize; h++) {
|
||
float ex = event.getHistoricalX(h);
|
||
float ey = event.getHistoricalY(h);
|
||
float ep = event.getHistoricalPressure(h);
|
||
processHoverMouse(event, ex, ey, ep, action);
|
||
}
|
||
}
|
||
|
||
float ex = event.getX();
|
||
float ey = event.getY();
|
||
float ep = event.getPressure();
|
||
processHoverMouse(event, ex, ey, ep, action);
|
||
return true;
|
||
|
||
case MotionEvent.ACTION_UP:
|
||
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Event was not managed
|
||
return false;
|
||
}
|
||
|
||
private void processHoverMouse(MotionEvent event, float x,float y,float p, int action) {
|
||
|
||
//Log.d("VncCanvas", "Mouse Hover: " + x + "," + y);
|
||
|
||
if(Config.mouseMode == Config.MouseMode.External) {
|
||
|
||
// float x_margin = (SDLActivity.width - MainSDLActivity.vm_width * LimboSDLActivity.height / (float) LimboSDLActivity.vm_height) / 2;
|
||
// if (x < x_margin) {
|
||
// return;
|
||
// } else if (x > SDLActivity.width - x_margin) {
|
||
// return;
|
||
// }
|
||
|
||
//TODO:
|
||
//SDLActivity.onSDLNativeMouse(0, action, x, y);
|
||
processPointerEvent(event, false, false);
|
||
}
|
||
// else if (Config.mouseMode == Config.MouseMode.External_Alt){
|
||
// processHoverMouseAlt(x, y, p, action);
|
||
// }
|
||
}
|
||
|
||
}
|
||
|
||
public float scalingX = 1;
|
||
public float scalingY = 1;
|
||
|
||
public void setupScaleMode() {
|
||
if (parentView != null) {
|
||
setupScaleMode(parentView);
|
||
return;
|
||
}
|
||
|
||
if (getScaleType() == ScaleType.FIT_XY) {
|
||
post(() -> {
|
||
if (getWidth() == 0 || getHeight() == 0 || getImageWidth() == 0 || getImageHeight() == 0) return;
|
||
|
||
scalingX = (float) getWidth() / getImageWidth();
|
||
scalingY = (float) getHeight() / getImageHeight();
|
||
|
||
setScaleX(scalingX);
|
||
setScaleY(scalingY);
|
||
});
|
||
} else if (getScaleType() == ScaleType.FIT_CENTER || getScaleType() == ScaleType.FIT_START) {
|
||
setScaleX(1);
|
||
setScaleY(1);
|
||
}
|
||
}
|
||
|
||
View parentView;
|
||
|
||
public void setupScaleMode(View parent) {
|
||
parentView = parent;
|
||
|
||
if (getScaleType() == ScaleType.FIT_XY) {
|
||
post(() -> {
|
||
if (parent.getWidth() == 0 || parent.getHeight() == 0 || getImageWidth() == 0 || getImageHeight() == 0) return;
|
||
|
||
scalingX = (float) parent.getWidth() / getImageWidth();
|
||
scalingY = (float) parent.getHeight() / getImageHeight();
|
||
|
||
setScaleX(scalingX);
|
||
setScaleY(scalingY);
|
||
});
|
||
} else if (getScaleType() == ScaleType.FIT_CENTER || getScaleType() == ScaleType.FIT_START) {
|
||
setScaleX(1);
|
||
setScaleY(1);
|
||
}
|
||
}
|
||
|
||
public final ScaleType FULL_SCREEN_MODE = ScaleType.FIT_CENTER;
|
||
public final ScaleType STRETCH_TO_FIT_MODE = ScaleType.FIT_XY;
|
||
public final ScaleType ONE_TO_ONE_MODE = ScaleType.CENTER;
|
||
|
||
public void setScaleMode(ScaleType mode) {
|
||
setScaleType(mode);
|
||
setupScaleMode();
|
||
}
|
||
}
|