From 09f9654ccd4ff423549843644a3565803cd1fee2 Mon Sep 17 00:00:00 2001
From: An Bui <91354810+AnBui2004@users.noreply.github.com>
Date: Wed, 10 Jun 2026 15:13:21 +0700
Subject: [PATCH] 4.2.4
- Faster return to virtual machine.
- Fixed the frozen virtual machine startup dialog when using X11.
- Added audio support for the built-in X11 display.
- Improved notifications.
- Qemu 7.2.22 has been improved and requires manual updating with the Upgrade tool using a command in the Terminal.
---
.github/ISSUE_TEMPLATE/bug.yaml | 1 +
README.md | 2 +-
app/build.gradle | 4 +-
app/src/main/java/com/vectras/vm/StartVM.java | 2 +
.../java/com/vectras/vm/fcm/FCMService.java | 9 +-
.../com/vectras/vm/main/core/MainStartVM.java | 91 +++++++--
.../java/com/vectras/vm/x11/X11Activity.java | 192 +++++++++++-------
.../layout/activity_x11_display_settings.xml | 19 ++
app/src/main/res/values-vi/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
js/push_notification.js | 5 +-
qemu/7.2.22/upgrade.sh | 2 +-
web/data/UpdateConfig.json | 8 +-
13 files changed, 239 insertions(+), 98 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
index 17c03f6..1e7cf9c 100644
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -32,6 +32,7 @@ body:
options:
- Latest
- Beta
+ - 4.2.0
- 4.1.0
- 4.0.0
- 3.9.0
diff --git a/README.md b/README.md
index 88c741e..8785173 100644
--- a/README.md
+++ b/README.md
@@ -127,7 +127,7 @@ QEMU 8.2.0 - 3dfx (only for Vectras VM 2.9.5):
- [See them at the Internet Archive](https://archive.org/details/vectras-vm-x86_64.tar_202603nbab)
QEMU 7.2.22 - 3dfx (for Vectras VM 4.2.2+):
-- [For Android ARM (64-bit)](https://archive.org/download/qemu-7-2-22-for-vectras-vm-nbab/base-vectras-vm-arm64-v8a.tar.gz)
+- [For Android ARM (64-bit)](https://archive.org/download/qemu-7-2-22-for-vectras-vm-nbab/base-june-2026-vectras-vm-arm64-v8a.tar.gz)
- [See more at the Internet Archive](https://archive.org/details/qemu-7-2-22-for-vectras-vm-nbab)
### 💽 3Dfx Wrappers
diff --git a/app/build.gradle b/app/build.gradle
index d6aead5..c492ebe 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vectras.vm"
minSdk minApi
targetSdk targetApi
- versionCode 127
- versionName "4.2.3"
+ versionCode 128
+ versionName "4.2.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
diff --git a/app/src/main/java/com/vectras/vm/StartVM.java b/app/src/main/java/com/vectras/vm/StartVM.java
index a4743bf..0a95509 100644
--- a/app/src/main/java/com/vectras/vm/StartVM.java
+++ b/app/src/main/java/com/vectras/vm/StartVM.java
@@ -28,6 +28,8 @@ public class StartVM {
private static DataMainRoms vmConfigs;
public static String env(Activity activity, DataMainRoms vmData) {
+ if (VMManager.isVMRunning(activity, vmData.vmID)) return "";
+
vmConfigs = vmData;
if (VMManager.isNeedLoadMigrate() && FileUtils.isFileExists(VmFileManager.getSnapshotSh(Config.vmID))) {
diff --git a/app/src/main/java/com/vectras/vm/fcm/FCMService.java b/app/src/main/java/com/vectras/vm/fcm/FCMService.java
index 94082d3..6188e01 100644
--- a/app/src/main/java/com/vectras/vm/fcm/FCMService.java
+++ b/app/src/main/java/com/vectras/vm/fcm/FCMService.java
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.vectras.vm.R;
+import com.vectras.vm.settings.UpdaterActivity;
import com.vectras.vm.utils.NotificationUtils;
import java.util.Map;
@@ -21,6 +22,12 @@ public class FCMService
&& !Objects.requireNonNull(data.get("targetVersions")).contains(getString(R.string.app_version))) {
return;
}
+
+ Class> activityClass = null;
+
+ if (data.get("activityClass") != null) {
+ if (Objects.equals(data.get("activityClass"), "update")) activityClass = UpdaterActivity.class;
+ }
NotificationUtils.pushNow(this,
1,
@@ -31,7 +38,7 @@ public class FCMService
data.get("image") != null ? data.get("image") : null,
-1,
data.get("url") != null ? data.get("url") : null,
- null);
+ activityClass);
}
@Override
diff --git a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java
index a6ad183..64b25fa 100644
--- a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java
+++ b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java
@@ -14,7 +14,6 @@ import android.widget.Toast;
import com.vectras.qemu.Config;
import com.vectras.qemu.MainSettingsManager;
import com.vectras.qemu.MainVNCActivity;
-import com.vectras.vm.AppConfig;
import com.vectras.vm.MainService;
import com.vectras.vm.R;
import com.vectras.vm.StartVM;
@@ -55,6 +54,26 @@ public class MainStartVM {
private static StartVmDialog dialog;
+ public static final int PENDDING_EMPTY = -1;
+
+ public static final int STARTED_VM = 0;
+ public static final int STARTED_VM_READY_RUNNING = 1;
+
+ public static final int ERROR_VM = 10;
+ public static final int ERROR_BREAK = 11;
+ public static final int ERROR_INVALID_VM_ID = 12;
+ public static final int ERROR_MAKE_TEMP_FOLDER = 13;
+ public static final int ERROR_UNSAFE_COMMAND = 14;
+ public static final int ERROR_ACCEL = 15;
+ public static final int ERROR_VNC_TCP_PORT = 16;
+ public static final int ERROR_SHARED_FOLDER = 17;
+ public static final int ERROR_RESUME = 18;
+
+ public interface MainStartVMCallback {
+ void onStarted(int statusCode, String message);
+ void onError(int errorCode, String message);
+ }
+
public static void startNow(Activity activity, DataMainRoms vmConfig) {
breakNow = false;
@@ -62,7 +81,10 @@ public class MainStartVM {
VMManager.setArch(vmConfig.itemArch, activity);
- if (!(MainSettingsManager.getVmUi(activity).equals("X11") && DisplaySystem.isUseBuiltInX11())) {
+ if (
+ MainSettingsManager.getVmUi(activity).equals("VNC") ||
+ (MainSettingsManager.getVmUi(activity).equals("X11") && !DisplaySystem.isUseBuiltInX11())
+ ) {
showDialog(activity, vmConfig.vmID, vmConfig.itemName, vmConfig.itemIcon, activity.getString(R.string.preparing));
}
@@ -82,9 +104,22 @@ public class MainStartVM {
String thumbnailFile,
StartVmDialog dialog
) {
+ startNow(context, vmName, env, vmID, thumbnailFile, dialog, null);
+ }
+
+ public static void startNow(
+ Context context,
+ String vmName,
+ String env,
+ String vmID,
+ String thumbnailFile,
+ StartVmDialog dialog,
+ MainStartVMCallback callback
+ ) {
if (breakNow) {
breakNow = false;
+ if (callback != null) callback.onError(PENDDING_EMPTY, "");
return;
}
@@ -92,7 +127,10 @@ public class MainStartVM {
if (isLaunchFromPending) {
isLaunchFromPending = false;
- if (pendingVMID.isEmpty()) return;
+ if (pendingVMID.isEmpty()) {
+ if (callback != null) callback.onError(ERROR_INVALID_VM_ID, "");
+ return;
+ }
pendingVMID = "";
} else {
if (MainSettingsManager.getVmUi(context).equals("X11") && !DisplaySystem.isUseBuiltInX11()) {
@@ -128,8 +166,6 @@ public class MainStartVM {
breakNow = false;
- if (dialog == null || !dialog.isShowing()) showDialog((Activity) context, vmID, vmName, thumbnailFile, null);
-
String finalvmID;
if (vmID == null || vmID.isEmpty()) {
finalvmID = VMManager.startRamdomVMID();
@@ -140,14 +176,23 @@ public class MainStartVM {
Config.vmID = finalvmID;
if (VMManager.isVMRunning(context, finalvmID)) {
- Toast.makeText(context, "This VM is already running.", Toast.LENGTH_LONG).show();
- DisplaySystem.launch(context);
- if (!MainSettingsManager.getVmUi(context).equals("VNC")) VmAudioManager.stream(vmID);
-
dismissDialog();
+
+ Toast.makeText(context, context.getString(R.string.this_vm_is_already_running), Toast.LENGTH_LONG).show();
+ DisplaySystem.launch(context);
+ if (
+ !MainSettingsManager.getVmUi(context).equals("VNC") &&
+ !DisplaySystem.isUseBuiltInX11()
+ )
+ VmAudioManager.stream(vmID);
+
+ if (callback != null) callback.onStarted(STARTED_VM_READY_RUNNING, "");
return;
}
+ // Place it here to avoid freezing when the dialog box appears upon returning to the virtual machine.
+ if (dialog == null || !dialog.isShowing()) showDialog((Activity) context, vmID, vmName, thumbnailFile, null);
+
File romDir = new File(Config.getCacheDir() + "/" + finalvmID);
if (!romDir.exists()) {
if (!romDir.mkdirs()) {
@@ -159,6 +204,7 @@ public class MainStartVM {
);
dismissDialog();
+ if (callback != null) callback.onError(ERROR_MAKE_TEMP_FOLDER, "");
return;
}
}
@@ -172,6 +218,7 @@ public class MainStartVM {
);
dismissDialog();
+ if (callback != null) callback.onError(ERROR_UNSAFE_COMMAND, "");
return;
}
@@ -182,6 +229,7 @@ public class MainStartVM {
() -> startNow(context, vmName, env.replace("tcg,thread=multi", "tcg,thread=single"), finalvmID, thumbnailFile, finalDialog1), null, null);
dismissDialog();
+ if (callback != null) callback.onError(ERROR_ACCEL, "");
return;
}
}
@@ -198,6 +246,7 @@ public class MainStartVM {
null);
dismissDialog();
+ if (callback != null) callback.onError(ERROR_VNC_TCP_PORT, "");
return;
}
@@ -221,6 +270,7 @@ public class MainStartVM {
);
dismissDialog();
+ if (callback != null) callback.onError(ERROR_SHARED_FOLDER, "");
return;
}
@@ -228,10 +278,11 @@ public class MainStartVM {
if (breakNow) {
dismissDialog();
+ if (callback != null) callback.onError(ERROR_BREAK, "");
return;
}
- startVm(context, vmName, env, finalvmID, thumbnailFile);
+ startVm(context, vmName, env, finalvmID, thumbnailFile, callback);
});
}).start();
}
@@ -241,7 +292,8 @@ public class MainStartVM {
String vmName,
String env,
String vmID,
- String thumbnailFile
+ String thumbnailFile,
+ MainStartVMCallback callback
) {
VMManager.isQemuStopedWithError = false;
@@ -257,6 +309,7 @@ public class MainStartVM {
if (breakNow) {
dismissDialog();
+ if (callback != null) callback.onError(ERROR_BREAK, "");
return;
}
@@ -332,6 +385,8 @@ public class MainStartVM {
null,
null
);
+
+ if (callback != null) callback.onError(ERROR_RESUME, "");
});
return;
@@ -372,12 +427,18 @@ public class MainStartVM {
Log.i(TAG, "Virtual machine running.");
if (activity != null) {
- activity.runOnUiThread(MainStartVM::dismissDialog);
+ activity.runOnUiThread(() -> {
+ MainStartVM.dismissDialog();
+ if (callback != null) callback.onStarted(STARTED_VM, "");
+ });
}
}).start();
} else {
assert activity != null;
- activity.runOnUiThread(MainStartVM::dismissDialog);
+ activity.runOnUiThread(() -> {
+ MainStartVM.dismissDialog();
+ if (callback != null) callback.onError(ERROR_VM, "");
+ });
}
skipIDEwithARM64DialogInStartVM = false;
@@ -404,9 +465,9 @@ public class MainStartVM {
VMManager.isTryAgain = false;
}
- public static void startPending(Context context) {
+ public static void startPending(Context context, MainStartVMCallback callback) {
isLaunchFromPending = true;
- startNow(context, pendingVMName, pendingEnv, pendingVMID, pendingThumbnailFile, null);
+ startNow(context, pendingVMName, pendingEnv, pendingVMID, pendingThumbnailFile, null, callback);
}
private static void showDialog(Activity activity, String vmId, String vmName, String thumbnailPath, String status) {
diff --git a/app/src/main/java/com/vectras/vm/x11/X11Activity.java b/app/src/main/java/com/vectras/vm/x11/X11Activity.java
index 249d735..674047f 100644
--- a/app/src/main/java/com/vectras/vm/x11/X11Activity.java
+++ b/app/src/main/java/com/vectras/vm/x11/X11Activity.java
@@ -3,18 +3,19 @@ package com.vectras.vm.x11;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.app.Dialog;
+
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import android.content.pm.PackageManager;
import android.os.Looper;
+
import static android.view.InputDevice.KEYBOARD_TYPE_ALPHABETIC;
import static android.view.KeyEvent.*;
import static android.view.WindowManager.LayoutParams.*;
import android.view.WindowManager;
-import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.FragmentTransaction;
@@ -28,6 +29,9 @@ import com.vectras.vm.databinding.DesktopControlsBinding;
import com.vectras.vm.databinding.GameControlsBinding;
import com.vectras.vm.main.core.MainStartVM;
import com.vectras.vm.manager.QmpSender;
+import com.vectras.vm.manager.VmAudioManager;
+import com.vectras.vm.manager.VmFileManager;
+import com.vectras.vm.utils.StreamAudio;
import com.vectras.vm.utils.UIUtils;
import static com.vectras.vm.x11.CmdEntryPoint.ACTION_START;
@@ -119,6 +123,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
public boolean ctrlClicked = false;
public boolean altClicked = false;
+ private StreamAudio streamAudio;
+
ActivityX11Binding binding;
ControlsFragmentBinding bindingControls;
DesktopControlsBinding bindingDesktopControls;
@@ -178,11 +184,11 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
@Override
@SuppressLint({
- "AppCompatMethod",
- "ObsoleteSdkInt",
- "ClickableViewAccessibility",
- "WrongConstant",
- "UnspecifiedRegisterReceiverFlag"
+ "AppCompatMethod",
+ "ObsoleteSdkInt",
+ "ClickableViewAccessibility",
+ "WrongConstant",
+ "UnspecifiedRegisterReceiverFlag"
})
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -281,8 +287,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
}
if (e.getScanCode() == KEY_BACK
- && e.getDevice().getKeyboardType()
- != KEYBOARD_TYPE_ALPHABETIC
+ && e.getDevice().getKeyboardType()
+ != KEYBOARD_TYPE_ALPHABETIC
|| e.getScanCode() == 0) {
if (toggleIMEUsingBackKey && e.getAction() == ACTION_UP) {
Log.d(TAG, "KEYCODE_BACK: toggleIMEUsingBackKey");
@@ -688,31 +694,49 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
bindingDesktopControls.ctrlaltdelBtn.setVisibility(View.GONE);
- MainStartVM.startPending(this);
+ MainStartVM.startPending(this, new MainStartVM.MainStartVMCallback() {
+ @Override
+ public void onStarted(int statusCode, String message) {
+ MainStartVM.dismissDialog();
+ setupSound();
+ }
+
+ @Override
+ public void onError(int errorCode, String message) {
+ MainStartVM.dismissDialog();
+ if (errorCode == MainStartVM.PENDDING_EMPTY) setupSound();
+ }
+ }
+ );
}
private void onBack() {
if (bindingControls.mainControl.getVisibility() == View.GONE) {
bindingControls.mainControl.setVisibility(View.VISIBLE);
} else {
+ if (streamAudio != null) {
+ if (!VmAudioManager.currentVmId.equals(Config.vmID)) streamAudio.setCross(null);
+ if (streamAudio.isPlaying()) streamAudio.stop();
+ }
finish();
}
}
-
+
private void keyDownUp(int keyEventCode) {
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
}
-
+
private void sendKey(int keyEventCode, boolean up) {
if (up)
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
else dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
}
-
+
@Override
protected void onDestroy() {
unregisterReceiver(receiver);
+ if (streamAudio != null) streamAudio.stop();
super.onDestroy();
}
@@ -790,7 +814,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
v.startDragAndDrop(
ClipData.newPlainText("", ""),
new View.DragShadowBuilder(binding.buttonVisibility) {
- public void onDrawShadow(@NonNull Canvas canvas) {}
+ public void onDrawShadow(@NonNull Canvas canvas) {
+ }
},
null,
View.DRAG_FLAG_GLOBAL);
@@ -927,48 +952,46 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
startTime = SystemClock.uptimeMillis();
binding.mouseButtonsPosition.setPressed(true);
break;
- case MotionEvent.ACTION_MOVE:
- {
- int[] offset = new int[2];
- int[] offset2 = new int[2];
- binding.mouseButtons.getLocationOnScreen(offset);
- binding.frame.getLocationOnScreen(offset2);
- binding.mouseButtons.setX(
- MathUtils.clamp(
- offset[0] - startOffset[0] + e.getX(),
- offset2[0],
- offset2[0]
- + binding.frame.getWidth()
- - binding.mouseButtons.getWidth()));
- binding.mouseButtons.setY(
- MathUtils.clamp(
- offset[1] - startOffset[1] + e.getY(),
- offset2[1],
- offset2[1]
- + binding.frame.getHeight()
- - binding.mouseButtons.getHeight()));
- break;
- }
- case MotionEvent.ACTION_UP:
- {
- final int[] _pos = new int[2];
- binding.mouseButtons.getLocationOnScreen(_pos);
- int deltaX =
- (int) (startOffset[0] - e.getX())
- + (startPosition[0] - _pos[0]);
- int deltaY =
- (int) (startOffset[1] - e.getY())
- + (startPosition[1] - _pos[1]);
- binding.mouseButtonsPosition.setPressed(false);
+ case MotionEvent.ACTION_MOVE: {
+ int[] offset = new int[2];
+ int[] offset2 = new int[2];
+ binding.mouseButtons.getLocationOnScreen(offset);
+ binding.frame.getLocationOnScreen(offset2);
+ binding.mouseButtons.setX(
+ MathUtils.clamp(
+ offset[0] - startOffset[0] + e.getX(),
+ offset2[0],
+ offset2[0]
+ + binding.frame.getWidth()
+ - binding.mouseButtons.getWidth()));
+ binding.mouseButtons.setY(
+ MathUtils.clamp(
+ offset[1] - startOffset[1] + e.getY(),
+ offset2[1],
+ offset2[1]
+ + binding.frame.getHeight()
+ - binding.mouseButtons.getHeight()));
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ final int[] _pos = new int[2];
+ binding.mouseButtons.getLocationOnScreen(_pos);
+ int deltaX =
+ (int) (startOffset[0] - e.getX())
+ + (startPosition[0] - _pos[0]);
+ int deltaY =
+ (int) (startOffset[1] - e.getY())
+ + (startPosition[1] - _pos[1]);
+ binding.mouseButtonsPosition.setPressed(false);
- if (deltaX * deltaX + deltaY * deltaY < touchSlop
- && SystemClock.uptimeMillis() - startTime
- <= tapTimeout) {
- v.performClick();
- return true;
- }
- break;
+ if (deltaX * deltaX + deltaY * deltaY < touchSlop
+ && SystemClock.uptimeMillis() - startTime
+ <= tapTimeout) {
+ v.performClick();
+ return true;
}
+ break;
+ }
}
return true;
}
@@ -1067,8 +1090,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
binding.mouseButtons
.setVisibility(
p.getBoolean("showMouseHelper", false)
- && "1".equals(p.getString("touchMode", "1"))
- && mClientConnected
+ && "1".equals(p.getString("touchMode", "1"))
+ && mClientConnected
? View.VISIBLE
: View.GONE);
@@ -1146,11 +1169,11 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
37.5f
* getResources().getDisplayMetrics().density
* (mExtraKeys.getExtraKeysInfo() == null
- ? 0
- : mExtraKeys
- .getExtraKeysInfo()
- .getMatrix()
- .length));
+ ? 0
+ : mExtraKeys
+ .getExtraKeysInfo()
+ .getMatrix()
+ .length));
terminalToolbarViewPager.setLayoutParams(layoutParams);
}
binding.frame.setPadding(
@@ -1158,8 +1181,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
0,
0,
preferences.getBoolean("adjustHeightForEK", false)
- && terminalToolbarViewPager.getVisibility()
- == View.VISIBLE
+ && terminalToolbarViewPager.getVisibility()
+ == View.VISIBLE
? terminalToolbarViewPager.getHeight()
: 0);
},
@@ -1213,8 +1236,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
public boolean handleKey(KeyEvent e) {
if (filterOutWinKey
&& (e.getKeyCode() == KEYCODE_META_LEFT
- || e.getKeyCode() == KEYCODE_META_RIGHT
- || e.isMetaPressed())) return false;
+ || e.getKeyCode() == KEYCODE_META_RIGHT
+ || e.isMetaPressed())) return false;
return mLorieKeyListener.onKey(getLorieView(), e.getKeyCode(), e);
}
@@ -1319,15 +1342,15 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
if (appOpsManager == null) return false;
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
return appOpsManager.unsafeCheckOpNoThrow(
- AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
- android.os.Process.myUid(),
- context.getPackageName())
+ AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
+ android.os.Process.myUid(),
+ context.getPackageName())
== AppOpsManager.MODE_ALLOWED;
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
return appOpsManager.checkOpNoThrow(
- AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
- android.os.Process.myUid(),
- context.getPackageName())
+ AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
+ android.os.Process.myUid(),
+ context.getPackageName())
== AppOpsManager.MODE_ALLOWED;
else
return false;
@@ -1384,8 +1407,8 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
binding.mouseButtons
.setVisibility(
p.getBoolean("showMouseHelper", false)
- && "1".equals(p.getString("touchMode", "1"))
- && mClientConnected
+ && "1".equals(p.getString("touchMode", "1"))
+ && mClientConnected
? View.VISIBLE
: View.GONE);
binding.stub
@@ -1433,6 +1456,31 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow
return mInputHandler.shouldInterceptKeys();
}
+ private void setupSound() {
+ if (streamAudio == null) {
+ streamAudio = new StreamAudio(X11Activity.this);
+ streamAudio.setFile(VmFileManager.findAudioRaw(X11Activity.this, Config.vmID));
+
+ if (VmAudioManager.currentVmId.equals(Config.vmID) && VmAudioManager.streamAudio.isPlaying())
+ streamAudio.setCross(VmAudioManager.streamAudio);
+
+ playSound();
+ }
+ }
+
+ int playSoundRequests;
+
+ private void playSound() {
+ if (streamAudio == null || streamAudio.isPlaying() || playSoundRequests > 0) return;
+ playSoundRequests++;
+
+ streamAudio.stop();
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ if (!streamAudio.isPlaying()) streamAudio.play();
+ playSoundRequests--;
+ }, 100);
+ }
+
private void shutdownthisvm() {
QmpSender.quickShutdown();
Config.setDefault();
diff --git a/app/src/main/res/layout/activity_x11_display_settings.xml b/app/src/main/res/layout/activity_x11_display_settings.xml
index 0acac86..9c08ef9 100644
--- a/app/src/main/res/layout/activity_x11_display_settings.xml
+++ b/app/src/main/res/layout/activity_x11_display_settings.xml
@@ -195,6 +195,25 @@
app:layout_constraintTop_toTopOf="parent"
android:clickable="false"/>
+
+