diff --git a/app/build.gradle b/app/build.gradle
index 99ab2af..1903722 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vectras.vm"
minSdk minApi
targetSdk targetApi
- versionCode 132
- versionName "4.2.8"
+ versionCode 133
+ versionName "4.2.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
diff --git a/app/src/main/java/android/androidVNC/VncCanvas.java b/app/src/main/java/android/androidVNC/VncCanvas.java
index af90d82..c65b323 100644
--- a/app/src/main/java/android/androidVNC/VncCanvas.java
+++ b/app/src/main/java/android/androidVNC/VncCanvas.java
@@ -465,15 +465,21 @@ public class VncCanvas extends AppCompatImageView {
}
}
- boolean fullUpdateNeeded = false;
-
- if (pendingColorModel != null) {
- setPixelFormat();
- fullUpdateNeeded = true;
- }
-
setEncodings(true);
- bitmapData.writeFullUpdateRequest(!fullUpdateNeeded);
+
+ if (isNeedWriteFullUpdateRequest) {
+ bitmapData.writeFullUpdateRequest(false);
+ isNeedWriteFullUpdateRequest = false;
+ } else {
+ boolean fullUpdateNeeded = false;
+
+ if (pendingColorModel != null) {
+ setPixelFormat();
+ fullUpdateNeeded = true;
+ }
+
+ bitmapData.writeFullUpdateRequest(!fullUpdateNeeded);
+ }
break;
@@ -2092,10 +2098,14 @@ public class VncCanvas extends AppCompatImageView {
bitmapData.updateBitmap(x, y, w, h);
}
+ boolean isNeedWriteFullUpdateRequest = true;
+
public void connected() {
+ // Request initial framebuffer update
+ isNeedWriteFullUpdateRequest = true;
+
VncCanvasActivity activity = (VncCanvasActivity) VncCanvas.this.getContext();
activity.onConnected();
-
}
public void disconnected() {
diff --git a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
index 836c7ce..1b45cfc 100644
--- a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
+++ b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java
@@ -6,6 +6,7 @@ import android.androidVNC.VncCanvasActivity;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.FragmentTransaction;
import android.annotation.SuppressLint;
@@ -74,6 +75,7 @@ import com.vectras.vm.utils.ListUtils;
import com.vectras.vm.utils.SimulateKeyEvent;
import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.utils.UIUtils;
+import com.vectras.vm.view.DynamicBubble;
import java.util.ArrayList;
import java.util.HashMap;
@@ -128,7 +130,7 @@ public class MainVNCActivity extends VncCanvasActivity {
new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (isConnected && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
InputDevice device = inputManager.getInputDevice(deviceId);
if (device != null && DeviceUtils.isMouseSource(device.getSources()) && Config.mouseMode == Config.MouseMode.Trackpad) {
setUIModeDesktop();
@@ -261,6 +263,12 @@ public class MainVNCActivity extends VncCanvasActivity {
});
if (!isConnected) tryReconnect(false);
+
+ DynamicBubble dynamicBubble = new DynamicBubble(binding.lnBubbleContainer, binding.btnBubble);
+ dynamicBubble.onClicked(() -> {
+ bindingControls.mainControl.setVisibility(bindingControls.mainControl.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
+ binding.btnBubble.setIcon(AppCompatResources.getDrawable(this, bindingControls.mainControl.getVisibility() == View.GONE ? R.drawable.radio_button_unchecked_24px : R.drawable.hide_source_24px));
+ });
}
private void setDefaulViewMode() {
@@ -540,23 +548,16 @@ public class MainVNCActivity extends VncCanvasActivity {
invalidateOptionsMenu();
UIUtils.setMouseVisible(binding.main);
+
+ if (firstConnection) bindingControls.mainControl.setVisibility(View.VISIBLE);
+ binding.lnBubbleContainer.setVisibility(View.GONE);
} catch (Exception ex) {
if (Config.debug)
Log.e(TAG, "setUIModeMobile: ", ex);
}
//Apply settings when connection is successful.
-// try {
-// vncCanvas.setupScaleMode(binding.main);
-// if (MainSettingsManager.getVNCScaleMode(this) == VNCConfig.oneToOne) {
-// vncCanvas.setScaleMode(vncCanvas.ONE_TO_ONE_MODE);
-// } else if (MainSettingsManager.getVNCScaleMode(this) == VNCConfig.scaleToFitScreen) {
-// vncCanvas.setScaleMode(vncCanvas.STRETCH_TO_FIT_MODE);
-// }
-// } catch (Exception e) {
-// MainSettingsManager.setVNCScaleMode(this, VNCConfig.fitToScreen);
-// Log.e(TAG, "oneToOne: ", e);
-// }
+ applyScaleMode();
}
private void promptSetUIModeDesktop(final Activity activity, final boolean mouseMethodAlt) {
@@ -595,10 +596,17 @@ public class MainVNCActivity extends VncCanvasActivity {
UIUtils.toastShort(MainVNCActivity.this, "External Mouse Enabled");
//onNormalScreen();
//AbstractScaling.getById(R.id.itemOneToOne).setScaleTypeForActivity(MainVNCActivity.this);
+
+ applyScaleMode();
+
showPanningState();
onMouse();
UIUtils.setMouseInvisible(binding.main);
+
+ if (firstConnection) bindingControls.mainControl.setVisibility(View.GONE);
+ binding.lnBubbleContainer.setVisibility(View.VISIBLE);
+ binding.btnBubble.setIcon(AppCompatResources.getDrawable(this, bindingControls.mainControl.getVisibility() == View.GONE ? R.drawable.radio_button_unchecked_24px : R.drawable.hide_source_24px));
} catch (Exception e) {
if (Config.debug)
Log.e(TAG, "setUIModeDesktop: ", e);
@@ -607,6 +615,20 @@ public class MainVNCActivity extends VncCanvasActivity {
invalidateOptionsMenu();
}
+ private void applyScaleMode() {
+ try {
+ vncCanvas.setupScaleMode(binding.main);
+ if (MainSettingsManager.getVNCScaleMode(this) == VNCConfig.oneToOne) {
+ vncCanvas.setScaleMode(vncCanvas.ONE_TO_ONE_MODE);
+ } else if (MainSettingsManager.getVNCScaleMode(this) == VNCConfig.scaleToFitScreen) {
+ vncCanvas.setScaleMode(vncCanvas.STRETCH_TO_FIT_MODE);
+ }
+ } catch (Exception e) {
+ MainSettingsManager.setVNCScaleMode(this, VNCConfig.fitToScreen);
+ Log.e(TAG, "oneToOne: ", e);
+ }
+ }
+
private boolean toggleFullScreen() {
UIUtils.toastShort(this, "VNC Fullscreen not supported");
@@ -841,7 +863,7 @@ public class MainVNCActivity extends VncCanvasActivity {
public void onBackPressed() {
if (bindingSendKey.sendkeylayout.getVisibility() == View.VISIBLE) {
bindingSendKey.sendkeylayout.setVisibility(View.GONE);
- } else if (bindingControls.mainControl.getVisibility() == View.GONE) {
+ } else if (binding.lnBubbleContainer.getVisibility() == View.GONE && bindingControls.mainControl.getVisibility() == View.GONE) {
bindingControls.mainControl.setVisibility(View.VISIBLE);
} else {
FrameLayout l = findViewById(R.id.mainControl);
@@ -863,14 +885,13 @@ public class MainVNCActivity extends VncCanvasActivity {
runOnUiThread(() -> {
isConnected = true;
this.resumeVM();
- if (!firstConnection)
- UIUtils.showHints(this);
- firstConnection = true;
+// if (!firstConnection)
+// UIUtils.showHints(this);
- if (Config.mouseMode == Config.MouseMode.External)
- setUIModeDesktop();
- else
- setUIModeMobile(screenMode == VNCScreenMode.FitToScreen);
+// if (Config.mouseMode == Config.MouseMode.External)
+// setUIModeDesktop();
+// else
+// setUIModeMobile(screenMode == VNCScreenMode.FitToScreen);
binding.lnNosignal.setVisibility(View.GONE);
binding.lnConnecting.setVisibility(View.GONE);
@@ -896,6 +917,8 @@ public class MainVNCActivity extends VncCanvasActivity {
setUIModeMobile(false);
}
}
+
+ firstConnection = true;
});
}
diff --git a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java
index 70a02f3..420d79c 100644
--- a/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java
+++ b/app/src/main/java/com/vectras/vm/manager/VmControllerDialog.java
@@ -543,7 +543,15 @@ public class VmControllerDialog extends DialogFragment {
if (uri != null) {
try {
new Thread(() -> {
- File selectedFilePath = new File(Objects.requireNonNull(FileUtils.getPath(getContext(), uri)));
+ String path = FileUtils.getPath(getContext(), uri);
+
+ if (path == null) {
+ showErrorSelectedFileDialog();
+ dismiss();
+ return;
+ }
+
+ File selectedFilePath = new File(path);
if (isAdded()) {
requireActivity().runOnUiThread(() -> {
diff --git a/app/src/main/java/com/vectras/vm/view/DynamicBubble.java b/app/src/main/java/com/vectras/vm/view/DynamicBubble.java
new file mode 100644
index 0000000..285d470
--- /dev/null
+++ b/app/src/main/java/com/vectras/vm/view/DynamicBubble.java
@@ -0,0 +1,90 @@
+package com.vectras.vm.view;
+
+import android.view.MotionEvent;
+import android.view.View;
+
+public class DynamicBubble {
+ View container;
+ View bubble;
+ private float dX, dY, startX, startY;
+ boolean isDragging;
+ final int CLICK_ACTION_THRESHOLD = 10;
+ Runnable onClicked;
+
+ public DynamicBubble(View container, View bubble) {
+ this.container = container;
+ this.bubble = bubble;
+
+ setup();
+ }
+
+ public void onClicked(Runnable onClicked) {
+ this.onClicked = onClicked;
+ }
+
+ private void setup() {
+ bubble.setOnTouchListener((v, event) -> {
+ switch (event.getActionMasked()) {
+
+ case MotionEvent.ACTION_DOWN:
+ // Save the View's initial position relative to your finger.
+ dX = v.getX() - event.getRawX();
+ dY = v.getY() - event.getRawY();
+
+ // Save the starting position to check if it's a click.
+ startX = event.getRawX();
+ startY = event.getRawY();
+ isDragging = false;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ // Calculate the distance moved.
+ float movedX = event.getRawX() - startX;
+ float movedY = event.getRawY() - startY;
+
+ // If the displacement exceeds the threshold, it is determined that pulling is occurring.
+ if (Math.abs(movedX) > CLICK_ACTION_THRESHOLD || Math.abs(movedY) > CLICK_ACTION_THRESHOLD) {
+ isDragging = true;
+
+ // Move the view using your finger.
+ v.setX(event.getRawX() + dX);
+ v.setY(event.getRawY() + dY);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (!isDragging) {
+ // Click event.
+ v.performClick();
+ if (onClicked != null) onClicked.run();
+ } else {
+ onDropped();
+ }
+ isDragging = false;
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ isDragging = false;
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ });
+ }
+
+ private void onDropped() {
+ if (bubble.getX() > (float) container.getWidth() / 2) {
+ bubble.animate().x(container.getWidth() - bubble.getWidth());
+ } else {
+ bubble.animate().x(0);
+ }
+
+ if (bubble.getY() < bubble.getHeight()) {
+ bubble.animate().y(0);
+ } else if (bubble.getY() > (float) container.getHeight() - bubble.getHeight()) {
+ bubble.animate().y(container.getHeight() - bubble.getHeight());
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/menu_24px.xml b/app/src/main/res/drawable/menu_24px.xml
new file mode 100644
index 0000000..538d1cf
--- /dev/null
+++ b/app/src/main/res/drawable/menu_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/radio_button_unchecked_24px.xml b/app/src/main/res/drawable/radio_button_unchecked_24px.xml
new file mode 100644
index 0000000..19ce240
--- /dev/null
+++ b/app/src/main/res/drawable/radio_button_unchecked_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_vnc.xml b/app/src/main/res/layout/activity_vnc.xml
index a8a9f9a..c4f7cfa 100644
--- a/app/src/main/res/layout/activity_vnc.xml
+++ b/app/src/main/res/layout/activity_vnc.xml
@@ -43,6 +43,21 @@
+
+
+
+
+
4.1.0\nBugs fixed.",
"cancellable": true,
- "versionCodeBeta":"132",
- "versionNameBeta":"4.2.8",
- "versionNameBetas":"4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,4.0.7,4.0.8,4.0.9,4.1.1,4.1.2,4.1.3,4.1.4,4.1.5,4.1.6,4.1.7,4.1.8,4.1.9,4.2.0,4.2.1,4.2.2,4.2.3,4.2.4,4.2.5,4.2.6,4.2.7,4.2.8",
+ "versionCodeBeta":"133",
+ "versionNameBeta":"4.2.9",
+ "versionNameBetas":"4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,4.0.7,4.0.8,4.0.9,4.1.1,4.1.2,4.1.3,4.1.4,4.1.5,4.1.6,4.1.7,4.1.8,4.1.9,4.2.0,4.2.1,4.2.2,4.2.3,4.2.4,4.2.5,4.2.6,4.2.7,4.2.8,4.2.9",
"sizeBeta": "45 MB",
"urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases",
- "MessageBeta": "4.2.8
Bugs fixed.",
+ "MessageBeta": "4.2.9
Bugs fixed.",
"cancellableBeta": true
}
diff --git a/web/data/vroms-store.json b/web/data/vroms-store.json
index d60a894..4c37b86 100644
--- a/web/data/vroms-store.json
+++ b/web/data/vroms-store.json
@@ -1641,6 +1641,26 @@
"vecid": "windows1124h2cvbi",
"id": ""
},
+ {
+ "rom_name": "Windows 11 24H2 ARM64",
+ "rom_icon": "https://www.getmyos.com/app_public/files/t/1/2021/06/windows_11_logo_by_getmyos.png",
+ "rom_url": "https://youtu.be/WL1uN41VKP0",
+ "rom_path": "Windows 11 24H2 ARM64.cvbi",
+ "rom_avail": true,
+ "gui": true,
+ "rom_size": "2024 Update",
+ "rom_arch": "ARM64",
+ "rom_kernel": "windows",
+ "rom_extra": "",
+ "final_rom_file_name": "",
+ "desc": "This Rom is from Nguyen Bao An Bui. You can get it on An Bui app: https://play.google.com/store/apps/details?id=com.anbui.app\n\nWindows 11 is the latest major release of Microsoft's Windows NT operating system, released on October 5, 2021 as the successor to Windows 10 (2015). It is provided free for any Windows 10 devices that meet the new Windows 11 system requirements. A server version, Windows Server 2025, was released in 2024. Windows 11 is the first major version of Windows NT without a companion mobile version following the discontinuations of Windows Phone with Windows 10 Mobile.",
+ "file_size": "10 GB",
+ "containsAds": true,
+ "creator": "Nguyen Bao An Bui",
+ "verified": true,
+ "vecid": "windows1124h2arm64cvbi",
+ "id": "99630"
+ },
{
"rom_name": "Windows 11 25H2",
"rom_icon": "https://www.getmyos.com/app_public/files/t/1/2021/06/windows_11_logo_by_getmyos.png",