mirror of
https://github.com/xoureldeen/Vectras-VM-Android.git
synced 2026-07-09 17:28:54 +00:00
4.2.0
- Added high-quality image option for VNC. - Added option to completely hide control buttons on the VNC screen. - Added battery emulation. - Added secondary optical drive. - Improved the virtual machine bootloader. - Fixed the issue where the socket file was not cleaned up after the virtual machine shut down. - Fixed other bugs.
This commit is contained in:
parent
bc6b438fdd
commit
67d8f2ce12
32 changed files with 825 additions and 251 deletions
|
|
@ -12,8 +12,8 @@ android {
|
|||
applicationId "com.vectras.vm"
|
||||
minSdk minApi
|
||||
targetSdk targetApi
|
||||
versionCode 123
|
||||
versionName "4.1.9"
|
||||
versionCode 124
|
||||
versionName "4.2.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
|
||||
|
|
|
|||
78
app/src/main/assets/roms/battery.asl
Normal file
78
app/src/main/assets/roms/battery.asl
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
DefinitionBlock ("battery.aml", "SSDT", 2, "QEMU", "BATTOS", 0x00000001)
|
||||
{
|
||||
Scope (\_SB)
|
||||
{
|
||||
|
||||
Device (EC0)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0C09"))
|
||||
|
||||
Method (_STA, 0, NotSerialized)
|
||||
{
|
||||
Return (0x0F)
|
||||
}
|
||||
}
|
||||
|
||||
Device (AC)
|
||||
{
|
||||
Name (_HID, "ACPI0003")
|
||||
|
||||
Method (_STA, 0, NotSerialized)
|
||||
{
|
||||
Return (0x0F)
|
||||
}
|
||||
|
||||
Method (_PSR, 0, NotSerialized)
|
||||
{
|
||||
Return (One) // Charging AC
|
||||
}
|
||||
}
|
||||
|
||||
Device (BAT0)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0C0A"))
|
||||
Name (_UID, One)
|
||||
|
||||
Name (_PCL, Package ()
|
||||
{
|
||||
\_SB
|
||||
})
|
||||
|
||||
Method (_STA, 0, NotSerialized)
|
||||
{
|
||||
Return (0x1F)
|
||||
}
|
||||
|
||||
Method (_BIF, 0, NotSerialized)
|
||||
{
|
||||
Return (Package ()
|
||||
{
|
||||
One, // Power Unit = mAh
|
||||
9000, // Design Capacity
|
||||
${last-full-charge-capacity}, // Last Full Charge Capacity
|
||||
One, // Rechargeable
|
||||
11111, // Design Voltage (11.111V)
|
||||
400, // Warning Capacity
|
||||
200, // Low Capacity
|
||||
100,
|
||||
100,
|
||||
"99Wh Laptop Battery",
|
||||
"202605",
|
||||
"LiIon",
|
||||
"VECTRAS_VM_BATTERY_EMULATOR"
|
||||
})
|
||||
}
|
||||
|
||||
Method (_BST, 0, NotSerialized)
|
||||
{
|
||||
Return (Package ()
|
||||
{
|
||||
${charging-status}, // Charging
|
||||
500, // Charge rate
|
||||
${remaining-capacity}, // Remaining capacity
|
||||
12600 // Current voltage
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -545,6 +545,11 @@ public class VncCanvas extends AppCompatImageView {
|
|||
|
||||
public void onDestroy() {
|
||||
Log.v(TAG, "Cleaning up resources");
|
||||
|
||||
if (rfb != null) {
|
||||
rfb.close();
|
||||
}
|
||||
|
||||
if (bitmapData != null) {
|
||||
bitmapData.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,10 @@ public class Config {
|
|||
return Config.getCacheDir()+ "/" + vmID + "/";
|
||||
}
|
||||
|
||||
public static String getCacheVMPath(String vmId) {
|
||||
return Config.getCacheDir()+ "/" + vmId + "/";
|
||||
}
|
||||
|
||||
public static enum MouseMode {
|
||||
Trackpad, External
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1240,4 +1240,16 @@ public class MainSettingsManager extends AppCompatActivity
|
|||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getBoolean("vncPinchToZoom", false);
|
||||
}
|
||||
|
||||
public static void setVncLosslessQuality(Context context, Boolean _boolean) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putBoolean("vncLosslessQuality", _boolean);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
public static Boolean getVncLosslessQuality(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getBoolean("vncLosslessQuality", true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.StrictMode;
|
||||
import android.os.SystemClock;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
|
|
@ -76,6 +75,11 @@ import com.vectras.vm.utils.UIUtils;
|
|||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
|
@ -106,8 +110,6 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
|
||||
public boolean ctrlClicked = false;
|
||||
public boolean altClicked = false;
|
||||
public static LinearLayout desktop;
|
||||
public static LinearLayout gamepad;
|
||||
private final ArrayList<HashMap<String, Object>> listmapForSendKey = new ArrayList<>();
|
||||
private boolean isConnected = false;
|
||||
|
||||
|
|
@ -165,18 +167,15 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
|
||||
onFitToScreen();
|
||||
|
||||
desktop = findViewById(R.id.desktop);
|
||||
gamepad = findViewById(R.id.gamepad);
|
||||
|
||||
if (Objects.equals(MainSettingsManager.getControlMode(this), "D")) {
|
||||
desktop.setVisibility(View.VISIBLE);
|
||||
gamepad.setVisibility(View.GONE);
|
||||
bindingControls.desktop.setVisibility(View.VISIBLE);
|
||||
bindingControls.gamepad.setVisibility(View.GONE);
|
||||
} else if (Objects.equals(MainSettingsManager.getControlMode(this), "G")) {
|
||||
desktop.setVisibility(View.GONE);
|
||||
gamepad.setVisibility(View.VISIBLE);
|
||||
bindingControls.desktop.setVisibility(View.GONE);
|
||||
bindingControls.gamepad.setVisibility(View.VISIBLE);
|
||||
} else if (Objects.equals(MainSettingsManager.getControlMode(this), "H")) {
|
||||
desktop.setVisibility(View.GONE);
|
||||
gamepad.setVisibility(View.GONE);
|
||||
bindingControls.desktop.setVisibility(View.GONE);
|
||||
bindingControls.gamepad.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
binding.lnNosignal.setOnClickListener(v -> {
|
||||
|
|
@ -791,9 +790,10 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
if (bindingSendKey.sendkeylayout.getVisibility() == View.VISIBLE) {
|
||||
bindingSendKey.sendkeylayout.setVisibility(View.GONE);
|
||||
} else if (bindingControls.mainControl.getVisibility() == View.GONE) {
|
||||
bindingControls.mainControl.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
FrameLayout l = findViewById(R.id.mainControl);
|
||||
if (l != null) {
|
||||
|
|
@ -805,7 +805,7 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
started = false;
|
||||
|
||||
if (streamAudio != null && !VmAudioManager.currentVmId.equals(Config.vmID)) streamAudio.setCross(null);
|
||||
finish();
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -872,13 +872,28 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
binding.lnNosignal.setVisibility(View.GONE);
|
||||
binding.lnConnecting.setVisibility(View.VISIBLE);
|
||||
|
||||
new Thread(() -> {
|
||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||
StrictMode.setThreadPolicy(policy);
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
Future<Boolean> future = executor.submit(() ->
|
||||
FileUtils.isFileExists(Config.getLocalQMPSocketPath())
|
||||
);
|
||||
|
||||
boolean isVMRunning = FileUtils.isFileExists(Config.getLocalQMPSocketPath());
|
||||
new Thread(() -> {
|
||||
boolean isVMRunning = false;
|
||||
try {
|
||||
isVMRunning = future.get(3, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
future.cancel(true);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "isQMPPortOpening: ", e);
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
if (isDestroyed() || isFinishing()) return;
|
||||
|
||||
boolean finalIsVMRunning = isVMRunning;
|
||||
runOnUiThread(() -> {
|
||||
if (isVMRunning) {
|
||||
if (finalIsVMRunning) {
|
||||
// Try reconnect.
|
||||
tryReconnect(true);
|
||||
} else if (isFinish) {
|
||||
|
|
@ -1028,12 +1043,7 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
return false;
|
||||
});
|
||||
|
||||
bindingControls.btnMode.setOnClickListener(v -> {
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
// Create and show the dialog.
|
||||
ControlersOptionsFragment newFragment = new ControlersOptionsFragment();
|
||||
newFragment.show(ft, "Controllers");
|
||||
});
|
||||
bindingControls.btnMode.setOnClickListener(v -> showSwitchControlLayoutDialog());
|
||||
|
||||
bindingControls.btnSettings.setOnClickListener(v -> {
|
||||
VmControllerDialog vmControllerDialog = new VmControllerDialog();
|
||||
|
|
@ -1567,4 +1577,12 @@ public class MainVNCActivity extends VncCanvasActivity {
|
|||
public String getPath(Uri uri) {
|
||||
return FileUtils.getPath(this, uri);
|
||||
}
|
||||
|
||||
private void showSwitchControlLayoutDialog() {
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
// Create and show the dialog.
|
||||
ControlersOptionsFragment newFragment = new ControlersOptionsFragment();
|
||||
newFragment.binding = binding.controlsfragment;
|
||||
newFragment.show(ft, "Controllers");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class AppConfig {
|
|||
// App Config
|
||||
public static String vectrasVersion;
|
||||
public static int vectrasVersionCode;
|
||||
public static final int standardSetupVersion = 922202605;
|
||||
public static final int standardSetupVersion = 922202606;
|
||||
public static String vectrasWebsite = "https://vectras.vercel.app/";
|
||||
public static String vectrasWebsiteRaw = "https://raw.githubusercontent.com/AnBui2004/Vectras-VM-Emu-Android/refs/heads/master/web/";
|
||||
public static String bootstrapfileslink = vectrasWebsiteRaw + "/data/setupfiles3.json";
|
||||
|
|
@ -82,13 +82,13 @@ public class AppConfig {
|
|||
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
|
||||
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
|
||||
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl mesa-gbm mesa-vulkan-ati mesa-vulkan-broadcom mesa-vulkan-freedreno mesa-vulkan-panfrost" +
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3";
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 iasl";
|
||||
} else {
|
||||
return "bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
|
||||
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
|
||||
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
|
||||
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl" +
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3";
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 iasl";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,13 +98,13 @@ public class AppConfig {
|
|||
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
|
||||
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
|
||||
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl" +
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3";
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 iasl";
|
||||
} else {
|
||||
return "bash aria2 tar dwm xterm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" +
|
||||
" libusbredirparser usbredir-dev sdl2 sdl2-dev sdl2_image-dev libepoxy-dev virglrenderer-dev rdma-core fluxbox" +
|
||||
" libusb libaio ncurses-libs curl libnfs gtk+3.0 gtk+3.0-dev fuse libpulse libseccomp jack pipewire liburing pulseaudio pulseaudio-alsa alsa-plugins-pulse" +
|
||||
" mesa-dri-gallium mesa-vulkan-swrast vulkan-loader mesa-utils mesa-egl" +
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 libatomic";
|
||||
" qemu-audio-sdl capstone libcbor snappy lzo ndctl keyutils-libs vde2-libs libdw libbpf sndio-libs linux-pam fuse3-libs libssh vte3 libatomic iasl";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
vmConfigMap.put("cores", current.cores);
|
||||
vmConfigMap.put("threads", current.threads);
|
||||
|
||||
vmConfigMap.put("battery", current.battery);
|
||||
|
||||
boolean isUsingDiskInQemuExtraParams = VMManager.isHaveADisk(current.itemExtra);
|
||||
|
||||
if (FileUtils.isFileExists(current.itemPath)) {
|
||||
|
|
@ -148,6 +150,12 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
vmConfigMap.put("cdrom", isUsingDiskInQemuExtraParams || FileUtils.isFileExists(Objects.requireNonNull(vmConfigMap.get("drive")).toString()) ? "" : VMManager.quickScanISOFileInFolder(getRomPath));
|
||||
}
|
||||
|
||||
if (FileUtils.isFileExists(current.cdrom1)) {
|
||||
vmConfigMap.put("cdrom1", new File(Objects.requireNonNull(Uri.parse(current.cdrom1).getPath())).getName());
|
||||
} else {
|
||||
vmConfigMap.put("cdrom1", isUsingDiskInQemuExtraParams || FileUtils.isFileExists(Objects.requireNonNull(vmConfigMap.get("drive")).toString()) ? "" : VMManager.quickScanISOFileInFolder(getRomPath));
|
||||
}
|
||||
|
||||
if (FileUtils.isFileExists(current.fda)) {
|
||||
vmConfigMap.put("fda", new File(Objects.requireNonNull(Uri.parse(current.fda).getPath())).getName());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ import android.view.Window;
|
|||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.qemu.MainVNCActivity;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.databinding.ControlsFragmentBinding;
|
||||
import com.vectras.vm.x11.X11Activity;
|
||||
|
||||
public class ControlersOptionsFragment extends DialogFragment {
|
||||
public ControlsFragmentBinding binding;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
|
|
@ -22,48 +24,48 @@ public class ControlersOptionsFragment extends DialogFragment {
|
|||
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
alertDialog.setContentView(R.layout.fragment_controlers_options);
|
||||
alertDialog.findViewById(R.id.gamepadBtn).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MainSettingsManager.setControlMode(getActivity(), "G");
|
||||
if (MainSettingsManager.getVmUi(getActivity()).equals("X11")) {
|
||||
if (X11Activity.desktop != null ) X11Activity.desktop.setVisibility(View.GONE);
|
||||
if (X11Activity.gamepad != null ) X11Activity.gamepad.setVisibility(View.VISIBLE);
|
||||
} else if (MainSettingsManager.getVmUi(getActivity()).equals("VNC")) {
|
||||
MainVNCActivity.desktop.setVisibility(View.GONE);
|
||||
MainVNCActivity.gamepad.setVisibility(View.VISIBLE);
|
||||
}
|
||||
alertDialog.cancel();
|
||||
alertDialog.findViewById(R.id.gamepadBtn).setOnClickListener(v -> {
|
||||
MainSettingsManager.setControlMode(getActivity(), "G");
|
||||
if (MainSettingsManager.getVmUi(getActivity()).equals("X11")) {
|
||||
if (X11Activity.desktop != null ) X11Activity.desktop.setVisibility(View.GONE);
|
||||
if (X11Activity.gamepad != null ) X11Activity.gamepad.setVisibility(View.VISIBLE);
|
||||
} else if (MainSettingsManager.getVmUi(getActivity()).equals("VNC")) {
|
||||
binding.desktop.setVisibility(View.GONE);
|
||||
binding.gamepad.setVisibility(View.VISIBLE);
|
||||
}
|
||||
alertDialog.cancel();
|
||||
});
|
||||
alertDialog.findViewById(R.id.desktopBtn).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MainSettingsManager.setControlMode(getActivity(), "D");
|
||||
if (MainSettingsManager.getVmUi(getActivity()).equals("X11")) {
|
||||
if (X11Activity.desktop != null ) X11Activity.desktop.setVisibility(View.VISIBLE);
|
||||
if (X11Activity.gamepad != null ) X11Activity.gamepad.setVisibility(View.GONE);
|
||||
} else if (MainSettingsManager.getVmUi(getActivity()).equals("VNC")) {
|
||||
MainVNCActivity.desktop.setVisibility(View.VISIBLE);
|
||||
MainVNCActivity.gamepad.setVisibility(View.GONE);
|
||||
}
|
||||
alertDialog.cancel();
|
||||
alertDialog.findViewById(R.id.desktopBtn).setOnClickListener(v -> {
|
||||
MainSettingsManager.setControlMode(getActivity(), "D");
|
||||
if (MainSettingsManager.getVmUi(getActivity()).equals("X11")) {
|
||||
if (X11Activity.desktop != null ) X11Activity.desktop.setVisibility(View.VISIBLE);
|
||||
if (X11Activity.gamepad != null ) X11Activity.gamepad.setVisibility(View.GONE);
|
||||
} else if (MainSettingsManager.getVmUi(getActivity()).equals("VNC")) {
|
||||
binding.desktop.setVisibility(View.VISIBLE);
|
||||
binding.gamepad.setVisibility(View.GONE);
|
||||
}
|
||||
alertDialog.cancel();
|
||||
});
|
||||
alertDialog.findViewById(R.id.hideBtn).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MainSettingsManager.setControlMode(getActivity(), "H");
|
||||
if (MainSettingsManager.getVmUi(getActivity()).equals("X11")) {
|
||||
if (X11Activity.desktop != null) X11Activity.desktop.setVisibility(View.GONE);
|
||||
if (X11Activity.gamepad != null) X11Activity.gamepad.setVisibility(View.GONE);
|
||||
} else if (MainSettingsManager.getVmUi(getActivity()).equals("VNC")) {
|
||||
if (MainVNCActivity.desktop != null) MainVNCActivity.desktop.setVisibility(View.GONE);
|
||||
if (MainVNCActivity.gamepad != null) MainVNCActivity.gamepad.setVisibility(View.GONE);
|
||||
}
|
||||
alertDialog.cancel();
|
||||
alertDialog.findViewById(R.id.hideBtn).setOnClickListener(v -> {
|
||||
MainSettingsManager.setControlMode(getActivity(), "H");
|
||||
if (MainSettingsManager.getVmUi(getActivity()).equals("X11")) {
|
||||
if (X11Activity.desktop != null) X11Activity.desktop.setVisibility(View.GONE);
|
||||
if (X11Activity.gamepad != null) X11Activity.gamepad.setVisibility(View.GONE);
|
||||
} else if (MainSettingsManager.getVmUi(getActivity()).equals("VNC")) {
|
||||
binding.desktop.setVisibility(View.GONE);
|
||||
binding.gamepad.setVisibility(View.GONE);
|
||||
}
|
||||
alertDialog.cancel();
|
||||
});
|
||||
|
||||
if (binding != null) {
|
||||
alertDialog.findViewById(R.id.hide_all).setVisibility(View.VISIBLE);
|
||||
|
||||
alertDialog.findViewById(R.id.hide_all).setOnClickListener(v -> {
|
||||
binding.mainControl.setVisibility(View.GONE);
|
||||
alertDialog.cancel();
|
||||
});
|
||||
}
|
||||
alertDialog.show();
|
||||
return alertDialog;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatDelegate;
|
|||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.vectras.qemu.Config;
|
||||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.qemu.utils.FileInstaller;
|
||||
import com.vectras.vm.crashtracker.LastCrashActivity;
|
||||
|
|
@ -117,6 +118,7 @@ public class SplashActivity extends AppCompatActivity {
|
|||
|
||||
private void cleanUp() {
|
||||
FileUtils.delete(AppConfig.vmFolder + "QuickRun");
|
||||
FileUtils.delete(Config.getCacheDir());
|
||||
VmFileManager.quickCleanUp(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.vectras.qemu.MainSettingsManager;
|
|||
import com.vectras.qemu.utils.RamInfo;
|
||||
import com.vectras.vm.creator.VMCreatorSelector;
|
||||
import com.vectras.vm.main.vms.DataMainRoms;
|
||||
import com.vectras.vm.manager.BatteryEmulatorManager;
|
||||
import com.vectras.vm.manager.QemuManager;
|
||||
import com.vectras.vm.manager.VmFileManager;
|
||||
import com.vectras.vm.settings.ItemSettingsSelector;
|
||||
|
|
@ -152,55 +153,51 @@ public class StartVM {
|
|||
params.add(hdd0);
|
||||
}
|
||||
|
||||
if (vmConfigs.imgCdrom.isEmpty()) {
|
||||
File cdromFile = new File(filesDir + "/data/Vectras/drive.iso");
|
||||
|
||||
if (cdromFile.exists()) {
|
||||
if (MainSettingsManager.getArch(activity).equals("ARM64")) {
|
||||
cdrom = " -drive";
|
||||
cdrom += " if=none,id=cdrom,format=raw,media=cdrom,file='" + cdromFile.getPath() + "'";
|
||||
cdrom += "-device";
|
||||
cdrom += " usb-storage,drive=cdrom";
|
||||
if (!extras.contains("-device nec-usb-xhci")) {
|
||||
cdrom += " -device";
|
||||
cdrom += " qemu-xhci";
|
||||
cdrom += " -device";
|
||||
cdrom += " nec-usb-xhci";
|
||||
}
|
||||
} else {
|
||||
if (ifType.isEmpty()) {
|
||||
cdrom = "-cdrom";
|
||||
cdrom += " '" + cdromFile.getPath() + "'";
|
||||
} else {
|
||||
cdrom = "-drive";
|
||||
cdrom += " media=cdrom";
|
||||
cdrom += ",file='" + cdromFile.getPath() + "'";
|
||||
}
|
||||
}
|
||||
|
||||
params.add(cdrom);
|
||||
}
|
||||
} else {
|
||||
if (!vmConfigs.imgCdrom.isEmpty()) {
|
||||
if (MainSettingsManager.getArch(activity).equals("ARM64")) {
|
||||
cdrom += " -device";
|
||||
cdrom += " nec-usb-xhci,id=defaultxhci";
|
||||
cdrom += " nec-usb-xhci,id=usbopticaldiscreader0";
|
||||
cdrom += " -device";
|
||||
cdrom += " usb-storage,bus=defaultxhci.0,drive=cdrom";
|
||||
cdrom += " usb-storage,bus=usbopticaldiscreader0.0,drive=cdromdrive0";
|
||||
cdrom += " -drive";
|
||||
cdrom += " if=none,id=cdrom,format=raw,media=cdrom,file='" + vmConfigs.imgCdrom + "'";
|
||||
cdrom += " if=none,id=cdromdrive0,format=raw,media=cdrom,file='" + vmConfigs.imgCdrom + "'";
|
||||
} else {
|
||||
if (ifType.isEmpty()) {
|
||||
if (!extras.contains("-cdrom ")) {
|
||||
cdrom = "-cdrom";
|
||||
cdrom += " '" + vmConfigs.imgCdrom + "'";
|
||||
} else {
|
||||
cdrom = "-drive";
|
||||
cdrom += " media=cdrom";
|
||||
cdrom += " media=cdromdrive1";
|
||||
cdrom += ",file='" + vmConfigs.imgCdrom + "'";
|
||||
}
|
||||
}
|
||||
params.add(cdrom);
|
||||
}
|
||||
|
||||
if (!vmConfigs.cdrom1.isEmpty()) {
|
||||
String cdromParams;
|
||||
String controllerId = vmConfigs.imgCdrom.isEmpty() ? "usbopticaldiscreader0" : "usbopticaldiscreader1";
|
||||
|
||||
if (MainSettingsManager.getArch(activity).equals("ARM64")) {
|
||||
cdromParams = " -device";
|
||||
cdromParams += " nec-usb-xhci,id=" + controllerId;
|
||||
cdromParams += " -device";
|
||||
cdromParams += " usb-storage,bus=" + controllerId + ".0,drive=cdromdrive1";
|
||||
cdromParams += " -drive";
|
||||
cdromParams += " if=none,id=cdromdrive1,format=raw,media=cdrom,file='" + vmConfigs.cdrom1 + "'";
|
||||
} else {
|
||||
if (vmConfigs.imgCdrom.isEmpty() && !extras.contains("-cdrom ")) {
|
||||
cdromParams = "-cdrom";
|
||||
cdromParams += " '" + vmConfigs.cdrom1 + "'";
|
||||
} else {
|
||||
cdromParams = "-drive";
|
||||
cdromParams += " media=cdromdrive1";
|
||||
cdromParams += ",file='" + vmConfigs.cdrom1 + "'";
|
||||
}
|
||||
}
|
||||
params.add(cdromParams);
|
||||
}
|
||||
|
||||
File hdd1File = new File(filesDir + "/data/Vectras/hdd1.qcow2");
|
||||
|
||||
if (hdd1File.exists()) {
|
||||
|
|
@ -348,6 +345,11 @@ public class StartVM {
|
|||
params.add("defer");
|
||||
}
|
||||
|
||||
if (vmConfigs.battery) {
|
||||
if (BatteryEmulatorManager.setup(activity, vmConfigs.vmID))
|
||||
params.add("-acpitable file=" + VmFileManager.getCompiledBatteryAcpi(activity, vmConfigs.vmID));
|
||||
}
|
||||
|
||||
return String.join(" ", params);
|
||||
}
|
||||
|
||||
|
|
@ -383,7 +385,7 @@ public class StartVM {
|
|||
|
||||
params += vncParams;
|
||||
} else {
|
||||
params += "unix:" + Config.getLocalVNCSocketPath() + ",lossy=off";
|
||||
params += "unix:" + Config.getLocalVNCSocketPath() + (MainSettingsManager.getVncLosslessQuality(context) ? ",lossy=off" : "");
|
||||
|
||||
if (QemuManager.isSupportSetRefreshRate(context))
|
||||
params += ",refresh-rate=" + ((context instanceof Activity activity) ? QemuManager.getAppropriateRefreshRate(activity, mainParams, DeviceUtils.getMaxRefreshRate(activity)) : Integer.parseInt(ItemSettingsSelector.getVncRefreshRateValue(MainSettingsManager.getVncRefreshRate(context))));
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
private int cpu = 0;
|
||||
private int cores = 0;
|
||||
private int threads = 0;
|
||||
private boolean isUseBattery = false;
|
||||
private boolean isShowBootMenu = false;
|
||||
private boolean isUseLocalTime = true;
|
||||
private boolean isUseUefi = false;
|
||||
|
|
@ -206,19 +207,6 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
|
||||
binding.lineardisclaimer.setOnClickListener(v -> DialogUtils.oneDialog(this, getResources().getString(R.string.dont_miss_out), getResources().getString(R.string.disclaimer_when_using_rom), getResources().getString(R.string.i_agree), true, R.drawable.verified_user_24px, true, null, null));
|
||||
|
||||
binding.cbvShowbootmenu.setOnCheckedChangeListener((v, isChecked) -> isShowBootMenu = isChecked);
|
||||
|
||||
binding.cbvUselocaltime.setOnCheckedChangeListener((v, isChecked) -> isUseLocalTime = isChecked);
|
||||
|
||||
binding.cbvUseuefi.setOnCheckedChangeListener((v, isChecked) -> isUseUefi = isChecked);
|
||||
|
||||
binding.cbvUseDefaultBios.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
isUseDefaultBios = isChecked;
|
||||
binding.cbvUseuefi.setEnabled(isChecked);
|
||||
});
|
||||
|
||||
if (!MainSettingsManager.getArch(this).equals("X86_64"))
|
||||
binding.cbvUseuefi.setVisibility(View.GONE);
|
||||
|
||||
binding.sbvCpu.setOnClickListener(v -> VMCreatorSelector.cpu(this, MainSettingsManager.getArch(this), cpu, ((position, name, value) -> {
|
||||
cpu = position;
|
||||
|
|
@ -235,11 +223,32 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
binding.sbvThread.setSubtitle(name);
|
||||
})));
|
||||
|
||||
binding.cbvBattery.setOnCheckedChangeListener((v, isChecked) -> isUseBattery = isChecked);
|
||||
|
||||
|
||||
|
||||
binding.sbvBootfrom.setOnClickListener(v -> VMCreatorSelector.bootFrom(this, bootFrom, ((position, name, value) -> {
|
||||
bootFrom = position;
|
||||
binding.sbvBootfrom.setSubtitle(name);
|
||||
})));
|
||||
|
||||
binding.cbvShowbootmenu.setOnCheckedChangeListener((v, isChecked) -> isShowBootMenu = isChecked);
|
||||
|
||||
binding.cbvUselocaltime.setOnCheckedChangeListener((v, isChecked) -> isUseLocalTime = isChecked);
|
||||
|
||||
if (!MainSettingsManager.getArch(this).equals("X86_64")) {
|
||||
binding.cbvUseuefi.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.cbvUseuefi.setOnCheckedChangeListener((v, isChecked) -> isUseUefi = isChecked);
|
||||
}
|
||||
|
||||
binding.cbvUseDefaultBios.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
isUseDefaultBios = isChecked;
|
||||
binding.cbvUseuefi.setEnabled(isChecked);
|
||||
});
|
||||
|
||||
|
||||
|
||||
VmFileManager.removeTemp(this, vmID, peddingTempFolder);
|
||||
|
||||
modify = getIntent().getBooleanExtra("MODIFY", false);
|
||||
|
|
@ -548,6 +557,10 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
setDrive(SELECT_CDROM_0_FILE_MODE, (current.imgCdrom.contains("/") ? "" : VmFileManager.getPath(vmID)).concat(current.imgCdrom));
|
||||
}
|
||||
|
||||
if (current.cdrom1 != null && !current.cdrom1.isEmpty()) {
|
||||
setDrive(SELECT_CDROM_1_FILE_MODE, (current.cdrom1.contains("/") ? "" : VmFileManager.getPath(vmID)).concat(current.cdrom1));
|
||||
}
|
||||
|
||||
if (current.itemIcon != null && !current.itemIcon.isEmpty()) {
|
||||
thumbnailPath = (current.itemIcon.contains("/") ? current.itemIcon : VmFileManager.getPath(vmID, current.itemIcon));
|
||||
updateThumbnailViewer("");
|
||||
|
|
@ -566,6 +579,9 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
threads = current.threads;
|
||||
binding.sbvThread.setSubtitle(String.valueOf(threads + 1));
|
||||
|
||||
isUseBattery = current.battery;
|
||||
binding.cbvBattery.setChecked(isUseBattery);
|
||||
|
||||
bootFrom = current.bootFrom;
|
||||
binding.sbvBootfrom.setSubtitle(Objects.requireNonNull(VMCreatorSelector.getBootFrom(this, current.bootFrom).get("name")).toString());
|
||||
isShowBootMenu = current.isShowBootMenu;
|
||||
|
|
@ -743,6 +759,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
current.cpu = cpu;
|
||||
current.cores = cores;
|
||||
current.threads = threads;
|
||||
current.battery = isUseBattery;
|
||||
|
||||
|
||||
current.itemPath = Objects.requireNonNull(binding.drive.getText()).toString();
|
||||
|
|
@ -752,6 +769,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
current.fdb = Objects.requireNonNull(binding.tieFdb.getText()).toString();
|
||||
|
||||
current.imgCdrom = Objects.requireNonNull(binding.cdrom.getText()).toString();
|
||||
current.cdrom1 = Objects.requireNonNull(binding.tieCdrom1.getText()).toString();
|
||||
|
||||
current.sharedFolder = sharedFolder;
|
||||
|
||||
|
|
@ -1224,6 +1242,10 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
binding.cdromField.setOnClickListener(v -> pickStorageFile(SELECT_CDROM_0_FILE_MODE));
|
||||
binding.cdromField.setEndIconOnClickListener(v -> setDrive(SELECT_CDROM_0_FILE_MODE, null));
|
||||
|
||||
binding.tieCdrom1.setOnClickListener(v -> pickStorageFile(SELECT_CDROM_1_FILE_MODE));
|
||||
binding.tilCdrom1.setOnClickListener(v -> pickStorageFile(SELECT_CDROM_1_FILE_MODE));
|
||||
binding.tilCdrom1.setEndIconOnClickListener(v -> setDrive(SELECT_CDROM_1_FILE_MODE, null));
|
||||
|
||||
|
||||
|
||||
binding.svSharedFolder.setSubTitle(AppConfig.sharedFolder);
|
||||
|
|
@ -1233,8 +1255,9 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
private final int SELECT_DISK_0_FILE_MODE = 0;
|
||||
private final int SELECT_DISK_1_FILE_MODE = 1;
|
||||
public final int SELECT_CDROM_0_FILE_MODE = 2;
|
||||
public final int SELECT_FLOPPY_A_FILE_MODE = 3;
|
||||
public final int SELECT_FLOPPY_B_FILE_MODE = 4;
|
||||
public final int SELECT_CDROM_1_FILE_MODE = 3;
|
||||
public final int SELECT_FLOPPY_A_FILE_MODE = 4;
|
||||
public final int SELECT_FLOPPY_B_FILE_MODE = 5;
|
||||
private int PENDING_SELECT_FILE_MODE = 0;
|
||||
private String PENDING_OLD_FILE_AFTER_SELECTED_NEW_FILE = "";
|
||||
|
||||
|
|
@ -1243,7 +1266,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
PENDING_OLD_FILE_AFTER_SELECTED_NEW_FILE = Objects.requireNonNull(getPeddingStorageEditText().getText()).toString();
|
||||
|
||||
try {
|
||||
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE) {
|
||||
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE) {
|
||||
isoPicker.launch("*/*");
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
floppyPicker.launch("*/*");
|
||||
|
|
@ -1329,13 +1352,13 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
peddingTextInputEditText.setText(path != null ? path : "");
|
||||
|
||||
if (path == null || path.isEmpty()) {
|
||||
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
peddingTextInputLayout.setEndIconMode(TextInputLayout.END_ICON_NONE);
|
||||
} else {
|
||||
peddingTextInputLayout.setEndIconDrawable(R.drawable.add_24px);
|
||||
}
|
||||
} else {
|
||||
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE || PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
peddingTextInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
|
||||
peddingTextInputLayout.setEndIconDrawable(R.drawable.close_24px);
|
||||
setRemovableDriveEndIconOnClickListener(PENDING_SELECT_FILE_MODE, peddingTextInputLayout);
|
||||
|
|
@ -1355,6 +1378,8 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
return binding.tilHd1;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE) {
|
||||
return binding.cdromField;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE) {
|
||||
return binding.tilCdrom1;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE) {
|
||||
return binding.tilFda;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
|
|
@ -1369,6 +1394,8 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
return binding.tieHd1;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_0_FILE_MODE) {
|
||||
return binding.cdrom;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_CDROM_1_FILE_MODE) {
|
||||
return binding.tieCdrom1;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_A_FILE_MODE) {
|
||||
return binding.tieFda;
|
||||
} else if (PENDING_SELECT_FILE_MODE == SELECT_FLOPPY_B_FILE_MODE) {
|
||||
|
|
@ -1382,6 +1409,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
return (Objects.requireNonNull(binding.drive.getText()).toString().isEmpty()) &&
|
||||
(Objects.requireNonNull(binding.tieHd1.getText()).toString().isEmpty()) &&
|
||||
(Objects.requireNonNull(binding.cdrom.getText()).toString().isEmpty()) &&
|
||||
(Objects.requireNonNull(binding.tieCdrom1.getText()).toString().isEmpty()) &&
|
||||
(Objects.requireNonNull(binding.tieFda.getText()).toString().isEmpty()) &&
|
||||
(Objects.requireNonNull(binding.tieFdb.getText()).toString().isEmpty());
|
||||
}
|
||||
|
|
@ -1392,6 +1420,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
String paramCollection = Objects.requireNonNull(binding.drive.getText()).toString();
|
||||
paramCollection += "\n" + Objects.requireNonNull(binding.tieHd1.getText());
|
||||
paramCollection += "\n" + Objects.requireNonNull(binding.cdrom.getText());
|
||||
paramCollection += "\n" + Objects.requireNonNull(binding.tieCdrom1.getText());
|
||||
paramCollection += "\n" + Objects.requireNonNull(binding.tieFda.getText());
|
||||
paramCollection += "\n" + Objects.requireNonNull(binding.tieFdb.getText());
|
||||
paramCollection += "\n" + Objects.requireNonNull(binding.qemu.getText());
|
||||
|
|
|
|||
|
|
@ -9,25 +9,18 @@ import android.os.Build;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
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;
|
||||
import com.vectras.vm.VMManager;
|
||||
import com.vectras.vm.logger.VectrasStatus;
|
||||
import com.vectras.vm.main.vms.DataMainRoms;
|
||||
import com.vectras.vm.manager.QmpSender;
|
||||
import com.vectras.vm.manager.VmFileManager;
|
||||
import com.vectras.vm.manager.VmAudioManager;
|
||||
|
|
@ -42,9 +35,8 @@ import java.io.File;
|
|||
|
||||
public class MainStartVM {
|
||||
public static final String TAG = "HomeStartVM";
|
||||
public static AlertDialog progressDialog;
|
||||
public static boolean skipIDEwithARM64DialogInStartVM = false;
|
||||
public static boolean isStopNow = false;
|
||||
public static boolean breakNow = false;
|
||||
public static final Handler handlerForLaunch = new Handler(Looper.getMainLooper());
|
||||
public static Runnable tickForLaunch = null;
|
||||
|
||||
|
|
@ -60,14 +52,41 @@ public class MainStartVM {
|
|||
public static boolean isLaunchFromPending = false;
|
||||
public static String runCommandFormat = "export TMPDIR=/tmp && mkdir -p $TMPDIR/pulse && export XDG_RUNTIME_DIR=/tmp && chmod -R 775 $TMPDIR/pulse && pulseaudio --start --exit-idle-time=-1 > /dev/null 2>&1 && %s";
|
||||
|
||||
private static StartVmDialog dialog;
|
||||
|
||||
public static void startNow(Activity activity, DataMainRoms vmConfig) {
|
||||
breakNow = false;
|
||||
|
||||
Config.vmID = vmConfig.vmID;
|
||||
|
||||
if (!(MainSettingsManager.getVmUi(activity).equals("X11") && DisplaySystem.isUseBuiltInX11())) {
|
||||
showDialog(activity, vmConfig.vmID, vmConfig.itemName, vmConfig.itemIcon, activity.getString(R.string.preparing));
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
VmFileManager.removeTemp(activity, vmConfig.vmID);
|
||||
|
||||
String env = StartVM.env(activity, vmConfig);
|
||||
activity.runOnUiThread(() -> startNow(activity, vmConfig.itemName, env, vmConfig.vmID, vmConfig.itemIcon, dialog));
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void startNow(
|
||||
Context context,
|
||||
String vmName,
|
||||
String env,
|
||||
String vmID,
|
||||
String thumbnailFile
|
||||
String thumbnailFile,
|
||||
StartVmDialog dialog
|
||||
) {
|
||||
|
||||
if (breakNow) {
|
||||
breakNow = false;
|
||||
return;
|
||||
}
|
||||
|
||||
setDefault();
|
||||
|
||||
if (isLaunchFromPending) {
|
||||
isLaunchFromPending = false;
|
||||
if (pendingVMID.isEmpty()) return;
|
||||
|
|
@ -104,7 +123,9 @@ public class MainStartVM {
|
|||
}
|
||||
}
|
||||
|
||||
isStopNow = false;
|
||||
breakNow = false;
|
||||
|
||||
if (dialog == null || !dialog.isShowing()) showDialog((Activity) context, vmID, vmName, thumbnailFile, null);
|
||||
|
||||
String finalvmID;
|
||||
if (vmID == null || vmID.isEmpty()) {
|
||||
|
|
@ -124,6 +145,8 @@ public class MainStartVM {
|
|||
context.getString(R.string.vm_cache_dir_failed_to_create_content),
|
||||
R.drawable.warning_48px
|
||||
);
|
||||
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,6 +158,8 @@ public class MainStartVM {
|
|||
context.getString(R.string.harmful_command_was_detected) + " " + context.getResources().getString(R.string.reason) + ": " + VMManager.latestUnsafeCommandReason,
|
||||
R.drawable.verified_user_24px
|
||||
);
|
||||
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +179,8 @@ public class MainStartVM {
|
|||
null,
|
||||
null
|
||||
);
|
||||
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -163,23 +190,31 @@ public class MainStartVM {
|
|||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppConfig.getSetupFiles().contains("arm") && !AppConfig.getSetupFiles().contains("arm64")) {
|
||||
if (env.contains("tcg,thread=multi")) {
|
||||
StartVmDialog finalDialog1 = dialog;
|
||||
DialogUtils.twoDialog(context, context.getResources().getString(R.string.problem_has_been_detected), context.getResources().getString(R.string.can_not_use_mttcg), context.getString(R.string.ok), context.getString(R.string.cancel), true, R.drawable.warning_48px, true,
|
||||
() -> startNow(context, vmName, env.replace("tcg,thread=multi", "tcg,thread=single"), finalvmID, thumbnailFile), null, null);
|
||||
() -> startNow(context, vmName, env.replace("tcg,thread=multi", "tcg,thread=single"), finalvmID, thumbnailFile, finalDialog1), null, null);
|
||||
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (MainSettingsManager.getArch(context).equals("ARM64") && MainSettingsManager.getIfType(context).equals("ide") && skipIDEwithARM64DialogInStartVM) {
|
||||
StartVmDialog finalDialog = dialog;
|
||||
DialogUtils.twoDialog(context, context.getString(R.string.problem_has_been_detected), context.getString(R.string.you_cannot_use_IDE_hard_drive_type_with_ARM64), context.getString(R.string.continuetext), context.getString(R.string.cancel), true, R.drawable.warning_48px, true,
|
||||
() -> {
|
||||
skipIDEwithARM64DialogInStartVM = true;
|
||||
startNow(context, vmName, env, finalvmID, thumbnailFile);
|
||||
startNow(context, vmName, env, finalvmID, thumbnailFile, finalDialog);
|
||||
}, null, null);
|
||||
|
||||
dismissDialog();
|
||||
return;
|
||||
} else if (skipIDEwithARM64DialogInStartVM) {
|
||||
skipIDEwithARM64DialogInStartVM = false;
|
||||
|
|
@ -199,16 +234,31 @@ public class MainStartVM {
|
|||
() -> context.startActivity(new Intent(context, ExternalVNCSettingsActivity.class)),
|
||||
null,
|
||||
null);
|
||||
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
VmFileManager.removeTemp(context, vmID);
|
||||
if (breakNow) {
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
TextView vmBootNote = showProgressDialog(context, vmName, thumbnailFile, vmID);
|
||||
startVm(context, vmName, env, finalvmID, thumbnailFile);
|
||||
}
|
||||
|
||||
public static void startVm(
|
||||
Context context,
|
||||
String vmName,
|
||||
String env,
|
||||
String vmID,
|
||||
String thumbnailFile
|
||||
) {
|
||||
VMManager.isQemuStopedWithError = false;
|
||||
|
||||
String finalCommand = VMManager.addAudioDevWav(vmID, String.format(runCommandFormat, env));
|
||||
String cleanUpCommand = "; rm -r " + Config.getCacheVMPath(vmID);
|
||||
|
||||
String finalCommand = VMManager.addAudioDevWav(vmID, String.format(runCommandFormat, env + cleanUpCommand));
|
||||
|
||||
if (MainSettingsManager.getVmUi(context).equals("X11")) {
|
||||
finalCommand = "export DISPLAY=:0 && " + finalCommand;
|
||||
|
|
@ -216,6 +266,11 @@ public class MainStartVM {
|
|||
}
|
||||
Log.i(TAG, finalCommand);
|
||||
|
||||
if (breakNow) {
|
||||
dismissDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServiceUtils.isServiceRunning(context, MainService.class)) {
|
||||
MainService.startCommand(finalCommand, context);
|
||||
} else {
|
||||
|
|
@ -233,19 +288,16 @@ public class MainStartVM {
|
|||
tickForLaunch = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isStopNow || VMManager.isQemuStopedWithError || FileUtils.isFileExists(Config.getLocalQMPSocketPath())) {
|
||||
if (breakNow || VMManager.isQemuStopedWithError || FileUtils.isFileExists(Config.getLocalQMPSocketPath())) {
|
||||
handlerForLaunch.removeCallbacks(this);
|
||||
|
||||
Activity activity = context instanceof Activity ? (Activity) context : null;
|
||||
|
||||
if (!isStopNow && !VMManager.isQemuStopedWithError) {
|
||||
if (!breakNow && !VMManager.isQemuStopedWithError) {
|
||||
new Thread(() -> {
|
||||
if (!forceDisableMigrate && VMManager.isNeedLoadMigrate()) {
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
if (vmBootNote != null)
|
||||
vmBootNote.setText(activity.getString(R.string.resuming));
|
||||
});
|
||||
activity.runOnUiThread(() -> dialog.setStatus(R.string.resuming));
|
||||
}
|
||||
String loadMigrateResponse = VMManager.loadMigrate();
|
||||
Log.d(TAG, "loadMigrateResponse: " + loadMigrateResponse);
|
||||
|
|
@ -268,8 +320,7 @@ public class MainStartVM {
|
|||
QmpSender.quickShutdown();
|
||||
assert activity != null;
|
||||
activity.runOnUiThread(() -> {
|
||||
if (DialogUtils.isSafeDismiss(activity, progressDialog))
|
||||
progressDialog.dismiss();
|
||||
activity.runOnUiThread(MainStartVM::dismissDialog);
|
||||
|
||||
DialogUtils.threeDialog(
|
||||
activity,
|
||||
|
|
@ -283,11 +334,11 @@ public class MainStartVM {
|
|||
true,
|
||||
() -> {
|
||||
forceDisableMigrate = true;
|
||||
startNow(context, vmName, env.replace("-incoming defer", ""), vmID, thumbnailFile);
|
||||
startNow(context, vmName, env.replace("-incoming defer", ""), vmID, thumbnailFile, dialog);
|
||||
},
|
||||
() -> {
|
||||
VMManager.deleteMigrate();
|
||||
startNow(context, vmName, env.replace("-incoming defer", ""), vmID, thumbnailFile);
|
||||
startNow(context, vmName, env.replace("-incoming defer", ""), vmID, thumbnailFile, dialog);
|
||||
},
|
||||
null,
|
||||
null
|
||||
|
|
@ -298,13 +349,14 @@ public class MainStartVM {
|
|||
}
|
||||
} else {
|
||||
forceDisableMigrate = false;
|
||||
if ( activity != null) activity.runOnUiThread(() -> dialog.setStatus(R.string.booting_up));
|
||||
}
|
||||
|
||||
QmpSender.resume();
|
||||
|
||||
if (MainSettingsManager.getVmUi(context).equals("VNC")) {
|
||||
if (MainSettingsManager.getVncExternal(context)) {
|
||||
Config.currentVNCServervmID = finalvmID;
|
||||
Config.currentVNCServervmID = vmID;
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> DialogUtils.oneDialog(context,
|
||||
context.getString(R.string.vnc_server),
|
||||
|
|
@ -331,16 +383,12 @@ public class MainStartVM {
|
|||
Log.i(TAG, "Virtual machine running.");
|
||||
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
if (DialogUtils.isSafeDismiss(activity, progressDialog))
|
||||
progressDialog.dismiss();
|
||||
});
|
||||
activity.runOnUiThread(MainStartVM::dismissDialog);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
assert activity != null;
|
||||
if (DialogUtils.isSafeDismiss(activity, progressDialog))
|
||||
progressDialog.dismiss();
|
||||
activity.runOnUiThread(MainStartVM::dismissDialog);
|
||||
}
|
||||
|
||||
skipIDEwithARM64DialogInStartVM = false;
|
||||
|
|
@ -360,70 +408,33 @@ public class MainStartVM {
|
|||
VectrasStatus.logInfo(i + ": " + params[i]);
|
||||
Log.d("HomeStartVM", i + ": " + params[i]);
|
||||
}
|
||||
|
||||
setDefault();
|
||||
}
|
||||
|
||||
public static void startTryAgain(Context context) {
|
||||
startNow(context, lastVMName, lastEnv, lastVMID, lastThumbnailFile);
|
||||
startNow(context, lastVMName, lastEnv, lastVMID, lastThumbnailFile, dialog);
|
||||
VMManager.isTryAgain = false;
|
||||
}
|
||||
|
||||
public static void startPending(Context context) {
|
||||
isLaunchFromPending = true;
|
||||
startNow(context, pendingVMName, pendingEnv, pendingVMID, pendingThumbnailFile);
|
||||
setDefault();
|
||||
startNow(context, pendingVMName, pendingEnv, pendingVMID, pendingThumbnailFile, null);
|
||||
}
|
||||
|
||||
public static TextView showProgressDialog(Context context, String _content, String thumbnailFile, String vmID) {
|
||||
View progressView = LayoutInflater.from(context).inflate(R.layout.dialog_start_vm, null);
|
||||
TextView tvVMName = progressView.findViewById(R.id.vm_name);
|
||||
TextView vmBootNote = progressView.findViewById(R.id.vm_boot_note);
|
||||
tvVMName.setText(_content);
|
||||
|
||||
if (thumbnailFile != null) {
|
||||
ImageView ivThumbnail = progressView.findViewById(R.id.iv_thumbnail);
|
||||
|
||||
if (!thumbnailFile.isEmpty() && FileUtils.isFileExists(thumbnailFile)) {
|
||||
Glide.with(context.getApplicationContext())
|
||||
.load(new File(thumbnailFile))
|
||||
.placeholder(R.drawable.ic_computer_180dp_with_padding)
|
||||
.error(R.drawable.ic_computer_180dp_with_padding)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(ivThumbnail);
|
||||
} else if (VmFileManager.isScreenshotPngExists(vmID)) {
|
||||
Glide.with(context.getApplicationContext())
|
||||
.load(new File(VmFileManager.getScreenshotPng(vmID)))
|
||||
.placeholder(R.drawable.ic_computer_180dp_with_padding)
|
||||
.error(R.drawable.ic_computer_180dp_with_padding)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(ivThumbnail);
|
||||
} else {
|
||||
VMManager.setIconWithName(ivThumbnail, _content);
|
||||
}
|
||||
private static void showDialog(Activity activity, String vmId, String vmName, String thumbnailPath, String status) {
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
ImageView ivStop = progressView.findViewById(R.id.ivStop);
|
||||
ivStop.setOnClickListener(v -> {
|
||||
isStopNow = true;
|
||||
ivStop.setVisibility(View.GONE);
|
||||
vmBootNote.setText(R.string.shutting_down);
|
||||
new Thread(() -> {
|
||||
QmpSender.shutdown();
|
||||
new Handler(Looper.getMainLooper()).post(() -> progressDialog.dismiss());
|
||||
}).start();
|
||||
});
|
||||
dialog = new StartVmDialog(activity, vmId, vmName, thumbnailPath);
|
||||
dialog.show(() -> breakNow = true);
|
||||
if (status != null) dialog.setStatus(status);
|
||||
}
|
||||
|
||||
progressDialog = new MaterialAlertDialogBuilder(context, R.style.CenteredDialogTheme)
|
||||
.setView(progressView)
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
|
||||
progressDialog.show();
|
||||
|
||||
return vmBootNote;
|
||||
private static void dismissDialog() {
|
||||
if (dialog != null) dialog.dismiss();
|
||||
if (breakNow) {
|
||||
new Thread(QmpSender::shutdown).start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setDefault() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.vectras.vm.AppConfig;
|
|||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.StartVM;
|
||||
import com.vectras.vm.VMManager;
|
||||
import com.vectras.vm.manager.VmFileManager;
|
||||
import com.vectras.vm.utils.DialogUtils;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vterm.Terminal;
|
||||
|
|
@ -59,10 +60,12 @@ public class PendingCommand {
|
|||
|
||||
Config.vmID = "quick_run_" + VMManager.idGenerator();
|
||||
new Thread(() -> {
|
||||
VmFileManager.removeTemp(activity, Config.vmID);
|
||||
|
||||
String env = StartVM.env(activity, command, "", true);
|
||||
FileUtils.createDirectory(AppConfig.vmFolder + Config.vmID);
|
||||
activity.runOnUiThread(() -> {
|
||||
MainStartVM.startNow(activity, "Quick run", env, Config.vmID, null);
|
||||
MainStartVM.startNow(activity, "Quick run", env, Config.vmID, null, null);
|
||||
VMManager.lastQemuCommand = command;
|
||||
command = "";
|
||||
});
|
||||
|
|
|
|||
146
app/src/main/java/com/vectras/vm/main/core/StartVmDialog.java
Normal file
146
app/src/main/java/com/vectras/vm/main/core/StartVmDialog.java
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package com.vectras.vm.main.core;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.vectras.qemu.Config;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.VMManager;
|
||||
import com.vectras.vm.manager.QmpSender;
|
||||
import com.vectras.vm.manager.VmFileManager;
|
||||
import com.vectras.vm.utils.DialogUtils;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class StartVmDialog {
|
||||
Activity activity;
|
||||
|
||||
AlertDialog progressDialog;
|
||||
View progressView;
|
||||
TextView tvVMName;
|
||||
TextView vmBootNote;
|
||||
|
||||
String vmName;
|
||||
String vmId;
|
||||
String thumbnailFile;
|
||||
|
||||
boolean isShuttingDown;
|
||||
|
||||
public interface StartVmDialogCallBack {
|
||||
void onStop();
|
||||
}
|
||||
|
||||
public StartVmDialog(Activity activity, String vmId, String vmName, String thumbnailFile) {
|
||||
this.activity = activity;
|
||||
this.vmName = vmName;
|
||||
this.vmId = vmId;
|
||||
this.thumbnailFile = thumbnailFile;
|
||||
}
|
||||
|
||||
public void show(StartVmDialogCallBack callBack) {
|
||||
progressView = LayoutInflater.from(activity).inflate(R.layout.dialog_start_vm, null);
|
||||
tvVMName = progressView.findViewById(R.id.vm_name);
|
||||
vmBootNote = progressView.findViewById(R.id.vm_boot_note);
|
||||
tvVMName.setText(vmName);
|
||||
|
||||
if (thumbnailFile != null) {
|
||||
ImageView ivThumbnail = progressView.findViewById(R.id.iv_thumbnail);
|
||||
|
||||
if (!thumbnailFile.isEmpty() && FileUtils.isFileExists(thumbnailFile)) {
|
||||
Glide.with(activity.getApplicationContext())
|
||||
.load(new File(thumbnailFile))
|
||||
.placeholder(R.drawable.ic_computer_180dp_with_padding)
|
||||
.error(R.drawable.ic_computer_180dp_with_padding)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(ivThumbnail);
|
||||
} else if (VmFileManager.isScreenshotPngExists(vmId)) {
|
||||
Glide.with(activity.getApplicationContext())
|
||||
.load(new File(VmFileManager.getScreenshotPng(vmId)))
|
||||
.placeholder(R.drawable.ic_computer_180dp_with_padding)
|
||||
.error(R.drawable.ic_computer_180dp_with_padding)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.into(ivThumbnail);
|
||||
} else {
|
||||
VMManager.setIconWithName(ivThumbnail, vmName);
|
||||
}
|
||||
}
|
||||
|
||||
ImageView ivStop = progressView.findViewById(R.id.ivStop);
|
||||
ivStop.setOnClickListener(v -> {
|
||||
isShuttingDown = true;
|
||||
ivStop.setVisibility(View.GONE);
|
||||
vmBootNote.setText(R.string.shutting_down);
|
||||
new Thread(() -> {
|
||||
QmpSender.shutdown();
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
callBack.onStop();
|
||||
});
|
||||
|
||||
boolean isVmRuning = VMManager.isVMRunning(activity, vmId);
|
||||
|
||||
while (isVmRuning) {
|
||||
Config.vmID = vmId;
|
||||
|
||||
isVmRuning = VMManager.isVMRunning(activity, vmId);
|
||||
|
||||
QmpSender.shutdown();
|
||||
|
||||
if (!isVmRuning) {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
isShuttingDown = false;
|
||||
dismiss();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
|
||||
progressDialog = new MaterialAlertDialogBuilder(activity, R.style.CenteredDialogTheme)
|
||||
.setView(progressView)
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
vmBootNote.setText(status);
|
||||
}
|
||||
|
||||
public void setStatus(int resourceId) {
|
||||
vmBootNote.setText(resourceId);
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return progressDialog != null && progressDialog.isShowing();
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
if (isShuttingDown) return;
|
||||
|
||||
DialogUtils.safeDismiss(activity, progressDialog);
|
||||
progressDialog = null;
|
||||
progressView = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,8 @@ public class DataMainRoms {
|
|||
)
|
||||
public String imgCdrom = "";
|
||||
|
||||
public String cdrom1 = "";
|
||||
|
||||
public String fda = "";
|
||||
|
||||
public String fdb = "";
|
||||
|
|
@ -64,4 +66,6 @@ public class DataMainRoms {
|
|||
public boolean isUseUefi = false;
|
||||
|
||||
public boolean isUseDefaultBios = true;
|
||||
|
||||
public boolean battery = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,12 +85,7 @@ public class VmsHomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||
} else {
|
||||
Config.QMPPort = current.qmpPort;
|
||||
}
|
||||
Config.vmID = current.vmID;
|
||||
|
||||
new Thread(() -> {
|
||||
String env = StartVM.env(activity, current);
|
||||
activity.runOnUiThread(() -> MainStartVM.startNow(activity, current.itemName, env, current.vmID, current.itemIcon));
|
||||
}).start();
|
||||
MainStartVM.startNow(activity, current);
|
||||
});
|
||||
|
||||
myHolder.cdRoms.setOnLongClickListener(v -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.vectras.vm.manager;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.vectras.vm.setupwizard.SetupFeatureCore;
|
||||
import com.vectras.vm.utils.DeviceUtils;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vterm.Terminal;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class BatteryEmulatorManager {
|
||||
private static final String TAG = "BatteryEmulatorManager";
|
||||
private static final int DESIGN_CAPACITY = 9000;
|
||||
private static final double PROPERTY_CAPACITY_DEPLETED_EACH_CYCLE = 0.04;
|
||||
|
||||
private static final String CHARGING_STATUS_CHARGING = "0x02";
|
||||
private static final String CHARGING_STATUS_PLUGGED_IN_BUT_NOT_CHARGING = "0x00";
|
||||
|
||||
private static final String DESIGN_CAPACITY_MARK = "${remaining-capacity}";
|
||||
private static final String LAST_FULL_CHARGE_CAPACITY_MARK = "${last-full-charge-capacity}";
|
||||
private static final String CHARGING_STATUS_MARK = "${charging-status}";
|
||||
|
||||
public static boolean setup(Context context, String vmId) {
|
||||
if (!QemuManager.isSupportAcpiTable(context)) return false;
|
||||
|
||||
int batteryPropertyCapacity = DeviceUtils.getBatteryPropertyCapacity(context);
|
||||
if (batteryPropertyCapacity < 1) batteryPropertyCapacity = 100;
|
||||
|
||||
int batteryCycleCount = DeviceUtils.getBatteryCycleCount(context);
|
||||
if (batteryCycleCount < 1) batteryCycleCount = 1;
|
||||
|
||||
File vmTemp = new File(VmFileManager.getTempPath(context, vmId));
|
||||
|
||||
try {
|
||||
String batteryAsl = SetupFeatureCore.readTextFromAssets(context, "roms/battery.asl");
|
||||
int remainingCapacity;
|
||||
int lastFullChargeCapacity = (int) (DESIGN_CAPACITY - ((double) (DESIGN_CAPACITY / 100) * (batteryCycleCount * PROPERTY_CAPACITY_DEPLETED_EACH_CYCLE)));
|
||||
|
||||
if (batteryCycleCount < 2) {
|
||||
remainingCapacity = DESIGN_CAPACITY / 100 * batteryPropertyCapacity;
|
||||
} else {
|
||||
remainingCapacity = lastFullChargeCapacity / 100 * batteryPropertyCapacity;
|
||||
}
|
||||
|
||||
batteryAsl = batteryAsl
|
||||
.replace(DESIGN_CAPACITY_MARK, String.valueOf(remainingCapacity))
|
||||
.replace(LAST_FULL_CHARGE_CAPACITY_MARK, String.valueOf(lastFullChargeCapacity))
|
||||
.replace(CHARGING_STATUS_MARK, batteryPropertyCapacity < 100 ? CHARGING_STATUS_CHARGING : CHARGING_STATUS_PLUGGED_IN_BUT_NOT_CHARGING);
|
||||
|
||||
FileUtils.writeToFile(vmTemp.getAbsolutePath(), "battery.asl", batteryAsl);
|
||||
|
||||
if (FileUtils.isFileExists(VmFileManager.getTempPath(context, vmId, "battery.asl"))) {
|
||||
String respond = Terminal.executeShellCommandWithResult("cd " + vmTemp.getAbsolutePath() + " && iasl battery.asl && echo Compiled", context);
|
||||
//Log.d(TAG, respond);
|
||||
return respond.contains("Compiled");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//Log.d(TAG, "Error: \n", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,12 @@ public class QemuManager {
|
|||
return Terminal.executeShellCommandWithResult(getQemuExecutableFile(context) + " -vnc help", context).contains("refresh-rate");
|
||||
}
|
||||
|
||||
public static boolean isSupportAcpiTable(Context context) {
|
||||
String currentArch = MainSettingsManager.getArch(context);
|
||||
|
||||
return currentArch.equals(MainSettingsManager.X86_64_ARCH) || currentArch.equals(MainSettingsManager.I386_ARCH);
|
||||
}
|
||||
|
||||
public static int getAppropriateRefreshRate(Context context, String params, int max) {
|
||||
int result = DEFAULT_REFRESH_RATE;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public class VmFileManager {
|
|||
public static final String SNAPSHOT_SH_FILE_NAME = "snapshot.sh";
|
||||
public static final String SNAPSHOT_BIN_FILE_NAME = "snapshot.bin";
|
||||
public static final String CREATE_COMMAND_CONFIG_FILE_NAME = "cqcm.json";
|
||||
public static final String COMPILED_BATERRY_ACPI_FILE_NAME = "battery.aml";
|
||||
public static final String LOG_FILE_NAME = "vm.log";
|
||||
public static final String TEXT_MARK_VM_PATH = "OhnoIjustrealizeditsmidnightandIstillhavetodothis";
|
||||
public static final String TEXT_MARK_EXTERNAL_DATA_PATH = "%external_data%";
|
||||
|
|
@ -234,6 +235,17 @@ public class VmFileManager {
|
|||
return VmFileManager.getAudioRaw(context, vmId);
|
||||
}
|
||||
|
||||
public static String getCompiledBatteryAcpi(Context context, String vmId) {
|
||||
return VmFileManager.getTempPath(context, vmId, COMPILED_BATERRY_ACPI_FILE_NAME);
|
||||
}
|
||||
|
||||
// Find the location of the aml file if getExternalCacheDir() is null.
|
||||
public static String findCompiledBatteryAcpi(Context context, String vmId) {
|
||||
String audioFilePath = VmFileManager.getInternalTempPath(context, vmId, COMPILED_BATERRY_ACPI_FILE_NAME);
|
||||
if (FileUtils.isFileExists(audioFilePath)) return audioFilePath;
|
||||
return VmFileManager.getAudioRaw(context, vmId);
|
||||
}
|
||||
|
||||
public static boolean removeAudioRaw(Context context, String vmId) {
|
||||
return VmFileManager.removeTemp(context, vmId, AUDIO_STREAM_FILE_NAME);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ public class VNCSettingsActivity extends AppCompatActivity {
|
|||
binding.swForcerefesh.setOnCheckedChangeListener((buttonView, isChecked) -> MainSettingsManager.setForceRefeshVNCDisplay(this, isChecked));
|
||||
binding.lnForcerefesh.setOnClickListener(v -> binding.swForcerefesh.toggle());
|
||||
|
||||
binding.swLossless.setOnCheckedChangeListener((buttonView, isChecked) -> MainSettingsManager.setVncLosslessQuality(this, isChecked));
|
||||
binding.lnForceLossless.setOnClickListener(v -> binding.swLossless.toggle());
|
||||
|
||||
binding.sbvRefreshRate.setOnClickListener(v -> ItemSettingsSelector.vncRefreshRate(this, MainSettingsManager.getVncRefreshRate(this), ((position, name, value) -> {
|
||||
MainSettingsManager.setVncRefreshRate(this, position);
|
||||
binding.sbvRefreshRate.setSubtitle(name);
|
||||
|
|
@ -61,6 +64,7 @@ public class VNCSettingsActivity extends AppCompatActivity {
|
|||
binding.lnExternal.setOnClickListener( v -> startActivity(new Intent(this, ExternalVNCSettingsActivity.class)));
|
||||
|
||||
binding.swForcerefesh.setChecked(MainSettingsManager.getForceRefeshVNCDisplay(this));
|
||||
binding.swLossless.setChecked(MainSettingsManager.getVncLosslessQuality(this));
|
||||
binding.sbvRefreshRate.setSubtitle(ItemSettingsSelector.getVncRefreshRateValue(MainSettingsManager.getVncRefreshRate(this)));
|
||||
binding.swExternal.setChecked(MainSettingsManager.getVncExternal(this));
|
||||
|
||||
|
|
@ -72,6 +76,7 @@ public class VNCSettingsActivity extends AppCompatActivity {
|
|||
private void uiController(boolean isEnabled) {
|
||||
binding.lnAllOptions.setAlpha(isEnabled ? 1f : 0.5f);
|
||||
binding.lnForcerefesh.setEnabled(isEnabled);
|
||||
binding.lnForceLossless.setEnabled(isEnabled);
|
||||
binding.sbvRefreshRate.setEnabled(isEnabled);
|
||||
binding.lnExternal.setEnabled(isEnabled);
|
||||
binding.swExternal.setEnabled(isEnabled);
|
||||
|
|
|
|||
|
|
@ -203,6 +203,20 @@ public class SetupFeatureCore {
|
|||
}
|
||||
}
|
||||
|
||||
public static String readTextFromAssets(Context context, String fileName) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(context.getAssets().open(fileName))
|
||||
);
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void setDNS(Context context) {
|
||||
String filesDir = context.getFilesDir().getAbsolutePath();
|
||||
File rootDir = new File(filesDir + "/distro/root");
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package com.vectras.vm.utils;
|
||||
|
||||
import static androidx.core.content.ContextCompat.registerReceiver;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.usage.StorageStatsManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.StatFs;
|
||||
|
|
@ -111,4 +116,24 @@ public class DeviceUtils {
|
|||
}
|
||||
return Math.round(maxHz);
|
||||
}
|
||||
|
||||
public static int getBatteryPropertyCapacity(Context context) {
|
||||
BatteryManager bm= (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
|
||||
return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
|
||||
}
|
||||
|
||||
public static int getBatteryCycleCount(Context context) {
|
||||
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
Intent batteryStatus = context.registerReceiver(null, filter);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
if (batteryStatus == null) return -1;
|
||||
|
||||
return batteryStatus.getIntExtra(
|
||||
BatteryManager.EXTRA_CYCLE_COUNT, -1
|
||||
);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
app/src/main/res/drawable/hide_source_24px.xml
Normal file
10
app/src/main/res/drawable/hide_source_24px.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M791,905L700,814Q651,846 595.5,863Q540,880 480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,420 97,364.5Q114,309 146,260L55,169L112,112L848,848L791,905ZM480,800Q523,800 563.5,789Q604,778 642,756L204,318Q182,356 171,396.5Q160,437 160,480Q160,613 253.5,706.5Q347,800 480,800ZM814,700L756,642Q778,604 789,563.5Q800,523 800,480Q800,347 706.5,253.5Q613,160 480,160Q437,160 396.5,171Q356,182 318,204L260,146Q309,114 364.5,97Q420,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,540 863,595.5Q846,651 814,700ZM537,423L537,423Q537,423 537,423Q537,423 537,423Q537,423 537,423Q537,423 537,423Q537,423 537,423Q537,423 537,423ZM423,537Q423,537 423,537Q423,537 423,537L423,537Q423,537 423,537Q423,537 423,537Q423,537 423,537Q423,537 423,537Z"/>
|
||||
</vector>
|
||||
|
|
@ -217,6 +217,18 @@
|
|||
app:title="@string/threads"
|
||||
app:subtitle="@string/defaulttext" />
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginHorizontal="16dp"/>
|
||||
|
||||
<com.vectras.vm.view.CheckBoxView
|
||||
android:id="@+id/cbv_battery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:setText="@string/battery"/>
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
|
@ -307,7 +319,7 @@
|
|||
android:paddingHorizontal="16dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:hint="@string/optical_drive"
|
||||
android:hint="@string/optical_drive_0"
|
||||
android:theme="@style/AppTheme.TextInput"
|
||||
app:endIconDrawable="@drawable/close_24px"
|
||||
app:startIconDrawable="@drawable/album_24px">
|
||||
|
|
@ -324,6 +336,30 @@
|
|||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/til_cdrom_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:hint="@string/optical_drive_1"
|
||||
android:theme="@style/AppTheme.TextInput"
|
||||
app:endIconDrawable="@drawable/close_24px"
|
||||
app:startIconDrawable="@drawable/album_24px">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/tie_cdrom_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:editable="false"
|
||||
android:focusable="false"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ln_floppy_container"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -82,6 +82,48 @@
|
|||
android:clickable="false"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/ln_force_lossless"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/object_shape_middle_high"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="2dp" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/sw_lossless"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/higher_quality"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/vnc_higher_quality_note"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/sw_lossless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:clickable="false"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.vectras.vm.view.SelectBoxView
|
||||
android:id="@+id/sbv_refresh_rate"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ln_tool_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/background1"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="18dp"
|
||||
android:layout_weight="1"
|
||||
android:indeterminate="true" />
|
||||
|
||||
|
|
|
|||
|
|
@ -3,39 +3,65 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/gamepadBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/gamepad"
|
||||
android:tint="?attr/colorPrimary" />
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/desktopBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/computer"
|
||||
android:tint="?attr/colorPrimary" />
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/hideBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/visibility_off"
|
||||
android:tint="?attr/colorPrimary" />
|
||||
<ImageButton
|
||||
android:id="@+id/gamepadBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/gamepad"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/desktopBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/computer"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/hideBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/visibility_off"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/hide_all"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/controls_button"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/hide_source_24px"
|
||||
app:tint="?attr/colorPrimary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
||||
|
|
@ -579,7 +579,13 @@
|
|||
<string name="hard_drive_0">Ổ cứng 0</string>
|
||||
<string name="hard_drive_1">Ổ cứng 1</string>
|
||||
<string name="optical_drive">Ổ quang</string>
|
||||
<string name="optical_drive_0">Ổ quang 0</string>
|
||||
<string name="optical_drive_1">Ổ quang 1</string>
|
||||
<string name="vm_creator_this_file_is_already_in_use_content">Tệp này đã được đùng ở một nơi khác của máy ảo. Bạn cần tìm và loại bỏ hoặc đổi tên nó và chọn lại.</string>
|
||||
<string name="higher_quality">Chất lượng cao hơn</string>
|
||||
<string name="vnc_higher_quality_note">Chất lượng hình ảnh sẽ được cải thiện nhưng độ trễ sẽ cao hơn nếu dùng ngoài.</string>
|
||||
<string name="battery">Pin</string>
|
||||
<string name="preparing">Đang chuẩn bị…</string>
|
||||
|
||||
<!--======================TERMUX STRINGS====================-->
|
||||
<string name="application_name" translatable="false">Vterm</string>
|
||||
|
|
|
|||
|
|
@ -590,7 +590,13 @@
|
|||
<string name="hard_drive_0">Hard drive 0</string>
|
||||
<string name="hard_drive_1">Hard drive 1</string>
|
||||
<string name="optical_drive">Optical drive</string>
|
||||
<string name="optical_drive_0">Optical drive 0</string>
|
||||
<string name="optical_drive_1">Optical drive 1</string>
|
||||
<string name="vm_creator_this_file_is_already_in_use_content">This file is already in use elsewhere on the virtual machine. You need to find and remove or rename it and select it again.</string>
|
||||
<string name="higher_quality">Higher quality</string>
|
||||
<string name="vnc_higher_quality_note">Image quality will improve, but the latency will be greater if using external.</string>
|
||||
<string name="battery">Battery</string>
|
||||
<string name="preparing">Preparing…</string>
|
||||
|
||||
|
||||
<!--======================TERMUX STRINGS====================-->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue