diff --git a/.idea/other.xml b/.idea/other.xml deleted file mode 100644 index b45a6e0..0000000 --- a/.idea/other.xml +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3cdd796..e37d87b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vectras.vm" minSdk minApi targetSdk targetApi - versionCode 20 - versionName "v2.9.4-jamtart" + versionCode 21 + versionName "v2.9.5-3dfx" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true } diff --git a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java index d7dd746..613c4f8 100644 --- a/app/src/main/java/com/vectras/qemu/MainVNCActivity.java +++ b/app/src/main/java/com/vectras/qemu/MainVNCActivity.java @@ -162,6 +162,8 @@ public class MainVNCActivity extends VncCanvasActivity { ImageButton rightGameBtn = findViewById(R.id.rightGameBtn); ImageButton enterGameBtn = findViewById(R.id.enterGameBtn); qmpBtn = findViewById(R.id.btnQmp); + ImageButton appsBtn = findViewById(R.id.btnPrograms); + appsBtn.setVisibility(View.GONE); upGameBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { diff --git a/app/src/main/java/com/vectras/vm/AboutActivity.java b/app/src/main/java/com/vectras/vm/AboutActivity.java index fde0b0a..3e364e8 100644 --- a/app/src/main/java/com/vectras/vm/AboutActivity.java +++ b/app/src/main/java/com/vectras/vm/AboutActivity.java @@ -38,6 +38,7 @@ import android.widget.TextView; import android.widget.Toast; import com.vectras.vm.R; +import com.vectras.vterm.Terminal; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -46,7 +47,7 @@ import java.net.URL; public class AboutActivity extends AppCompatActivity implements View.OnClickListener{ - Button btn_osl, btn_clog, btn_youtube, btn_github, btn_telegram, btn_instagram, btn_facebook; + Button btn_osl, btn_clog, btn_discord, btn_youtube, btn_github, btn_telegram, btn_instagram, btn_facebook; String appInfo; public String TAG = "AboutActivity"; @@ -66,6 +67,7 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList btn_github = (Button) findViewById(R.id.btn_github); btn_instagram = (Button) findViewById(R.id.btn_instagram); btn_facebook = (Button) findViewById(R.id.btn_facebook); + btn_discord = (Button) findViewById(R.id.btn_discord); btn_osl = (Button) findViewById(R.id.btn_osl); btn_clog = (Button) findViewById(R.id.btn_changelog); //onclicklistener @@ -74,6 +76,7 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList btn_youtube.setOnClickListener(this); btn_instagram.setOnClickListener(this); btn_facebook.setOnClickListener(this); + btn_discord.setOnClickListener(this); btn_osl.setOnClickListener(this); btn_clog.setOnClickListener(this); @@ -138,6 +141,22 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList GithubUserAdapter adapter = new GithubUserAdapter(this, usernames); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + + TextView qemuVersion = findViewById(R.id.qemuVersion); + + String command = "qemu-system-x86_64 --version"; + new Terminal(this).extractQemuVersion(command, false, this, (output, errors) -> { + if (errors.isEmpty()) { + String versionStr = "Unknown"; + if (output.equals("8.2.1")) + versionStr = output + " - 3dfx"; + Log.d(TAG, "QEMU Version: " + versionStr); + qemuVersion.setText(versionStr); + } else { + Log.e(TAG, "Errors: " + errors); + } + }); } @Override @@ -152,6 +171,7 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList public static final int GT = R.id.btn_github; public static final int IG = R.id.btn_instagram; public static final int FB = R.id.btn_facebook; + public static final int DD = R.id.btn_discord; public static final int CL = R.id.btn_changelog; public static final int OSL = R.id.btn_osl; @Override @@ -177,6 +197,11 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(ig)); startActivity(i); + } else if (id == DD) { + String dd = "https://discord.gg/t8TACrKSk7"; + Intent f = new Intent(Intent.ACTION_VIEW); + f.setData(Uri.parse(dd)); + startActivity(f); } else if (id == FB) { String fb = AppConfig.vectrasWebsite + "community.html"; Intent f = new Intent(Intent.ACTION_VIEW); diff --git a/app/src/main/java/com/vectras/vm/AppConfig.java b/app/src/main/java/com/vectras/vm/AppConfig.java index 7037afd..fa266e4 100644 --- a/app/src/main/java/com/vectras/vm/AppConfig.java +++ b/app/src/main/java/com/vectras/vm/AppConfig.java @@ -74,4 +74,9 @@ public class AppConfig { public static String vmFolder = maindirpath + "roms/"; public static String pendingCommand = ""; + public static String neededPkgs = "tar dwm xfce4-terminal libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" + + " libusbredirparser usbredir-dev libiscsi-dev sdl2 sdl2-dev libepoxy-dev virglrenderer-dev rdma-core" + + " libusb ncurses-libs curl libnfs sdl2 gtk+3.0 fuse libpulse libseccomp jack pipewire liburing" + + " 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"; + } diff --git a/app/src/main/java/com/vectras/vm/MainActivity.java b/app/src/main/java/com/vectras/vm/MainActivity.java index b651b8d..e065046 100644 --- a/app/src/main/java/com/vectras/vm/MainActivity.java +++ b/app/src/main/java/com/vectras/vm/MainActivity.java @@ -7,6 +7,7 @@ import com.termux.app.TermuxService; import static com.vectras.vm.VectrasApp.getApp; import static com.vectras.vm.VectrasApp.getAppInfo; +import static com.vectras.vm.utils.LibraryChecker.isPackageInstalled2; import static com.vectras.vm.utils.UIUtils.UIAlert; import android.app.ActivityManager; @@ -82,6 +83,7 @@ import com.vectras.vm.adapter.LogsAdapter; import com.vectras.vm.logger.VectrasStatus; import com.vectras.vm.utils.AppUpdater; import com.vectras.vm.utils.FileUtils; +import com.vectras.vm.utils.LibraryChecker; import com.vectras.vm.utils.UIUtils; import com.vectras.vterm.Terminal; @@ -99,8 +101,10 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; import com.google.gson.Gson; @@ -152,6 +156,8 @@ public class MainActivity extends AppCompatActivity { if (MainSettingsManager.getPromptUpdateVersion(activity)) updateApp(false); + new LibraryChecker(activity).checkMissingLibraries(activity); + romsLayout = findViewById(R.id.romsLayout); SwipeRefreshLayout refreshRoms = findViewById(R.id.refreshRoms); @@ -174,7 +180,7 @@ public class MainActivity extends AppCompatActivity { MainService.stopService(); Terminal vterm = new Terminal(activity); - vterm.executeShellCommand("killall qemu-system-*", false, activity); + vterm.executeShellCommand2("killall qemu-system-*", false, activity); extVncLayout.setVisibility(View.GONE); appbar.setExpanded(false); @@ -249,12 +255,12 @@ public class MainActivity extends AppCompatActivity { } if (id == R.id.navigation_item_help) { String tw = AppConfig.vectrasHelp; - Intent w = new Intent(Intent.ACTION_VIEW); + Intent w = new Intent(ACTION_VIEW); w.setData(Uri.parse(tw)); startActivity(w); } else if (id == R.id.navigation_item_website) { String tw = AppConfig.vectrasWebsite; - Intent w = new Intent(Intent.ACTION_VIEW); + Intent w = new Intent(ACTION_VIEW); w.setData(Uri.parse(tw)); startActivity(w); } else if (id == R.id.navigation_item_import_iso) { @@ -434,6 +440,52 @@ public class MainActivity extends AppCompatActivity { startActivityForResult(intent, 1006); } + } else if (id == R.id.navigation_item_desktop) { + if (SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle("X11 Feature Not Supported") + .setMessage("The X11 feature is currently not supported on Android 14 and above. Please use a device with Android 13 or below for X11 functionality.") + .setPositiveButton("OK", (dialog, which) -> dialog.dismiss()) + .create() + .show(); + } else { + // XFCE4 meta-package + String xfce4Package = "xfce4"; + + // Check if XFCE4 is installed + isPackageInstalled2(activity, xfce4Package, (output, errors) -> { + boolean isInstalled = false; + + // Check if the package exists in the installed packages output + if (output != null) { + Set installedPackages = new HashSet<>(); + for (String installedPackage : output.split("\n")) { + installedPackages.add(installedPackage.trim()); + } + + isInstalled = installedPackages.contains(xfce4Package.trim()); + } + + // If not installed, show a dialog to install it + if (!isInstalled) { + new AlertDialog.Builder(activity, R.style.MainDialogTheme) + .setTitle("Install XFCE4") + .setMessage("XFCE4 is not installed. Would you like to install it?") + .setCancelable(false) + .setPositiveButton("Install", (dialog, which) -> { + String installCommand = "apk add " + xfce4Package; + new Terminal(activity).executeShellCommand(installCommand, true, activity); + }) + .setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()) + .show(); + } else { + new Terminal(activity).executeShellCommand2("killall xfce4-session", false, activity); + startActivity(new Intent(activity, X11Activity.class)); + new Terminal(activity).executeShellCommand2("xfce4-session", false, MainActivity.activity); + } + }); + + } } else if (id == R.id.navigation_item_terminal) { /*com.vectras.vterm.TerminalBottomSheetDialog VTERM = new com.vectras.vterm.TerminalBottomSheetDialog(activity); VTERM.showVterm();*/ @@ -448,8 +500,8 @@ public class MainActivity extends AppCompatActivity { Timer _timer = new Timer(); TimerTask t; - LinearLayoutManager layoutManager = new LinearLayoutManager(VectrasApp.getApp()); - LogsAdapter mLogAdapter = new LogsAdapter(layoutManager, VectrasApp.getApp()); + LinearLayoutManager layoutManager = new LinearLayoutManager(getApp()); + LogsAdapter mLogAdapter = new LogsAdapter(layoutManager, getApp()); RecyclerView logList = (RecyclerView) view.findViewById(R.id.recyclerLog); logList.setAdapter(mLogAdapter); logList.setLayoutManager(layoutManager); @@ -495,7 +547,7 @@ public class MainActivity extends AppCompatActivity { startActivity(new Intent(activity, DataExplorerActivity.class)); } else if (id == R.id.navigation_item_donate) { String tw = "https://www.patreon.com/VectrasTeam"; - Intent w = new Intent(Intent.ACTION_VIEW); + Intent w = new Intent(ACTION_VIEW); w.setData(Uri.parse(tw)); startActivity(w); } else if (id== R.id.setup_sound) { @@ -550,7 +602,7 @@ public class MainActivity extends AppCompatActivity { VectrasApp.killallqemuprocesses(getApplicationContext()); VectrasApp.deleteDirectory(AppConfig.vmFolder); VectrasApp.deleteDirectory(AppConfig.recyclebin); - File vDir = new File(com.vectras.vm.AppConfig.maindirpath); + File vDir = new File(AppConfig.maindirpath); vDir.mkdirs(); errorjsondialog(); } @@ -618,7 +670,7 @@ public class MainActivity extends AppCompatActivity { } }); if (Config.debug) - UIUtils.UIAlert(activity, getString(R.string.debug_testing_build_5), getString(R.string.welcome_to_debug_build_of_vectras_vm_br) + + UIAlert(activity, getString(R.string.debug_testing_build_5), getString(R.string.welcome_to_debug_build_of_vectras_vm_br) + getString(R.string.this_version_unstable_and_has_alot_of_bugs_br) + getString(R.string.don_t_forget_to_tell_us_on_github_issues_or_telegram_bot_br) + getString(R.string.a_href_https_t_me_vectras_protect_bot_telegram_report_bot_a_br) + @@ -632,7 +684,7 @@ public class MainActivity extends AppCompatActivity { alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.join), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { String tg = "https://t.me/vectras_os"; - Intent f = new Intent(Intent.ACTION_VIEW); + Intent f = new Intent(ACTION_VIEW); f.setData(Uri.parse(tg)); startActivity(f); return; @@ -739,6 +791,21 @@ public class MainActivity extends AppCompatActivity { _timer.scheduleAtFixedRate(t, (int) (0), (int) (1000)); ShellExecutor shellExec = new ShellExecutor(); shellExec.exec(TermuxService.PREFIX_PATH + "/bin/termux-x11 :0"); + + TextView qemuVersion = findViewById(R.id.qemuVersion); + + String command = "qemu-system-x86_64 --version"; + new Terminal(activity).extractQemuVersion(command, false, activity, (output, errors) -> { + if (errors.isEmpty()) { + String versionStr = "Unknown"; + if (output.equals("8.2.1")) + versionStr = output + " - 3dfx"; + Log.d(TAG, "QEMU Version: " + versionStr); + qemuVersion.setText(versionStr); + } else { + Log.e(TAG, "Errors: " + errors); + } + }); } @Override @@ -778,7 +845,7 @@ public class MainActivity extends AppCompatActivity { .setNegativeButton("Update", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { try { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(obj.getString("url")))); + startActivity(new Intent(ACTION_VIEW, Uri.parse(obj.getString("url")))); } catch (JSONException e) { } @@ -1378,7 +1445,7 @@ public class MainActivity extends AppCompatActivity { } public String getPath(Uri uri) { - return com.vectras.vm.utils.FileUtils.getPath(this, uri); + return FileUtils.getPath(this, uri); } private void errorjsondialog() { diff --git a/app/src/main/java/com/vectras/vm/MainService.java b/app/src/main/java/com/vectras/vm/MainService.java index dda3d1b..6d9a343 100644 --- a/app/src/main/java/com/vectras/vm/MainService.java +++ b/app/src/main/java/com/vectras/vm/MainService.java @@ -13,6 +13,7 @@ import android.util.Log; import androidx.core.app.NotificationCompat; import com.vectras.qemu.MainVNCActivity; +import com.vectras.vm.core.PulseAudio; import com.vectras.vterm.Terminal; import java.io.File; @@ -48,10 +49,8 @@ public class MainService extends Service { if (service != null) { String filesDir = MainActivity.activity.getFilesDir().getAbsolutePath(); Terminal vterm = new Terminal(this); - //vterm.executeShellCommand("chmod 770 /run/pulse -R"); - //vterm.executeShellCommand("pulseaudio --system --disallow-exit --disallow-module-loading --daemonize --log-level=debug --log-time=1"); - vterm.executeShellCommand("dwm", false, MainActivity.activity); - vterm.executeShellCommand(env, true, MainActivity.activity); + vterm.executeShellCommand2("dwm", false, MainActivity.activity); + vterm.executeShellCommand2(env, false, MainActivity.activity); } } else Log.e(TAG, "env is null"); diff --git a/app/src/main/java/com/vectras/vm/SetupQemuActivity.java b/app/src/main/java/com/vectras/vm/SetupQemuActivity.java index a0e45a3..cc01bdb 100644 --- a/app/src/main/java/com/vectras/vm/SetupQemuActivity.java +++ b/app/src/main/java/com/vectras/vm/SetupQemuActivity.java @@ -609,9 +609,7 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick " echo \"Starting setup...\";" + " apk update;" + " echo \"Installing packages...\";" + - " apk add tar dwm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" + - " libusbredirparser usbredir-dev libiscsi-dev sdl2 sdl2-dev libepoxy-dev virglrenderer-dev rdma-core" + - " libusb ncurses-libs curl libnfs sdl2 gtk+3.0 fuse libpulse libseccomp jack pipewire liburing mesa-dri-gallium;" + + " apk add " + AppConfig.neededPkgs + ";" + " echo \"Installing Qemu...\";" + " tar -xzvf " + tarPath + " -C /;" + " rm " + tarPath + ";" + @@ -633,9 +631,7 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick " echo \"Starting setup...\";" + " apk update;" + " echo \"Installing packages...\";" + - " apk add tar dwm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" + - " libusbredirparser usbredir-dev libiscsi-dev sdl2 sdl2-dev libepoxy-dev virglrenderer-dev rdma-core" + - " libusb ncurses-libs curl libnfs sdl2 gtk+3.0 fuse libpulse libseccomp jack pipewire liburing;" + + " apk add " + AppConfig.neededPkgs + ";" + //" tar -xzvf " + tarPath + " -C /;" + " echo \"Installing Qemu...\";" + " apk add qemu-system-x86_64 qemu-system-ppc qemu-system-i386 qemu-system-aarch64 qemu-pr-helper qemu-img qemu-audio-sdl pulseaudio mesa-dri-gallium;" + @@ -657,9 +653,7 @@ public class SetupQemuActivity extends AppCompatActivity implements View.OnClick " echo \"Starting setup...\";" + " apk update;" + " echo \"Installing packages...\";" + - " apk add tar dwm libslirp libslirp-dev pulseaudio-dev glib-dev pixman-dev zlib-dev spice-dev" + - " libusbredirparser usbredir-dev libiscsi-dev sdl2 sdl2-dev libepoxy-dev virglrenderer-dev rdma-core" + - " libusb ncurses-libs curl libnfs sdl2 gtk+3.0 fuse libpulse libseccomp jack pipewire liburing mesa-dri-gallium;" + + " apk add " + AppConfig.neededPkgs + ";" + " echo \"Downloading Qemu...\";" + " curl -o setup.tar.gz -L " + bootstrapfilelink + ";" + " echo \"Installing Qemu...\";" + diff --git a/app/src/main/java/com/vectras/vm/VectrasApp.java b/app/src/main/java/com/vectras/vm/VectrasApp.java index 8c2f550..b068937 100644 --- a/app/src/main/java/com/vectras/vm/VectrasApp.java +++ b/app/src/main/java/com/vectras/vm/VectrasApp.java @@ -620,10 +620,10 @@ public class VectrasApp extends Application { public static void killallqemuprocesses(Context context) { Terminal vterm = new Terminal(context); - vterm.executeShellCommand("killall -9 qemu-system-i386", false, MainActivity.activity); - vterm.executeShellCommand("killall -9 qemu-system-x86_64", false, MainActivity.activity); - vterm.executeShellCommand("killall -9 qemu-system-aarch64", false, MainActivity.activity); - vterm.executeShellCommand("killall -9 qemu-system-ppc", false, MainActivity.activity); + vterm.executeShellCommand2("killall -9 qemu-system-i386", false, MainActivity.activity); + vterm.executeShellCommand2("killall -9 qemu-system-x86_64", false, MainActivity.activity); + vterm.executeShellCommand2("killall -9 qemu-system-aarch64", false, MainActivity.activity); + vterm.executeShellCommand2("killall -9 qemu-system-ppc", false, MainActivity.activity); } public static void killcurrentqemuprocess(Context context) { @@ -643,7 +643,7 @@ public class VectrasApp extends Application { env += "qemu-system-x86_64"; break; } - vterm.executeShellCommand(env, false, MainActivity.activity); + vterm.executeShellCommand2(env, false, MainActivity.activity); } public static boolean isAppInstalled(String packagename, Context context) { @@ -679,7 +679,7 @@ public class VectrasApp extends Application { public static boolean isQemuRunning() { Terminal vterm = new Terminal(MainActivity.activity); - vterm.executeShellCommand("ps -e", false, MainActivity.activity); + vterm.executeShellCommand2("ps -e", false, MainActivity.activity); if (TerminalOutput.contains("qemu-system")) { Log.d("VectrasApp.isQemuRunning", "Yes"); return true; @@ -691,7 +691,7 @@ public class VectrasApp extends Application { public static boolean isThisVMRunning(String intemExtra, String itemPath) { Terminal vterm = new Terminal(MainActivity.activity); - vterm.executeShellCommand("ps -e", false, MainActivity.activity); + vterm.executeShellCommand2("ps -e", false, MainActivity.activity); if (TerminalOutput.contains(intemExtra) && TerminalOutput.contains(itemPath)) { Log.d("VectrasApp.isThisVMRunning", "Yes"); return true; 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 dcba615..08b9048 100644 --- a/app/src/main/java/com/vectras/vm/x11/X11Activity.java +++ b/app/src/main/java/com/vectras/vm/x11/X11Activity.java @@ -11,6 +11,8 @@ 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 android.widget.TextView; import androidx.fragment.app.FragmentTransaction; import com.vectras.qemu.MainSettingsManager; @@ -80,6 +82,7 @@ import com.vectras.vm.x11.utils.KeyInterceptor; import com.vectras.vm.x11.utils.TermuxX11ExtraKeys; import com.vectras.vm.x11.utils.X11ToolbarViewPager; import com.vectras.vm.R; +import com.vectras.vterm.Terminal; import java.util.Map; import java.util.Objects; @@ -394,6 +397,7 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow ImageButton leftGameBtn = findViewById(R.id.leftGameBtn); ImageButton rightGameBtn = findViewById(R.id.rightGameBtn); ImageButton enterGameBtn = findViewById(R.id.enterGameBtn); + ImageButton appsBtn = findViewById(R.id.btnPrograms); qmpBtn = findViewById(R.id.btnQmp); @@ -415,6 +419,38 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow } }); + appsBtn.setOnClickListener(v -> { + Dialog dialog = new Dialog(activity); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setContentView(R.layout.dialog_programs); + dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); + + WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes(); + layoutParams.alpha = 1f; + dialog.getWindow().setAttributes(layoutParams); + + ImageButton termBtn = dialog.findViewById(R.id.btnTerminal); + ImageButton vkCubeBtn = dialog.findViewById(R.id.btnVkCube); + ImageButton glxGearsBtn = dialog.findViewById(R.id.btnGlxGears); + + termBtn.setOnClickListener(v1 -> { + new Terminal(activity).executeShellCommand2("xfce4-terminal", false, activity); + dialog.dismiss(); + }); + + glxGearsBtn.setOnClickListener(v1 -> { + new Terminal(activity).executeShellCommand2("glxgears", false, activity); + dialog.dismiss(); + }); + + vkCubeBtn.setOnClickListener(v1 -> { + new Terminal(activity).executeShellCommand2("vkcube", false, activity); + dialog.dismiss(); + }); + + dialog.show(); + }); + upGameBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -1424,7 +1460,13 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow } @Override - public void onBackPressed() {} + public void onBackPressed() { + if (findViewById(R.id.mainControl).getVisibility() == View.GONE) { + findViewById(R.id.mainControl).setVisibility(View.VISIBLE); + } else if (findViewById(R.id.mainControl).getVisibility() == View.VISIBLE) { + findViewById(R.id.mainControl).setVisibility(View.GONE); + } + } public static boolean hasPipPermission(@NonNull Context context) { AppOpsManager appOpsManager = diff --git a/app/src/main/java/com/vectras/vterm/Terminal.java b/app/src/main/java/com/vectras/vterm/Terminal.java index feb951a..6fa4089 100644 --- a/app/src/main/java/com/vectras/vterm/Terminal.java +++ b/app/src/main/java/com/vectras/vterm/Terminal.java @@ -1,6 +1,7 @@ package com.vectras.vterm; import android.app.Activity; +import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -12,6 +13,7 @@ import android.util.Log; import androidx.appcompat.app.AlertDialog; import com.termux.app.TermuxService; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -23,6 +25,8 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.vectras.qemu.MainSettingsManager; import com.vectras.qemu.MainVNCActivity; @@ -70,7 +74,7 @@ public class Terminal { AlertDialog dialog = new AlertDialog.Builder(activity, R.style.MainDialogTheme).create(); dialog.setTitle("Execution Result"); dialog.setMessage(message); - //.setPositiveButton("OK", (dialogInterface, i) -> dialogInterface.dismiss()) + //.setPositiveButton("OK", (dialogInterface, i) -> dialogInterface.dismiss()) dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //killQemuProcess(); @@ -89,6 +93,100 @@ public class Terminal { StringBuilder errors = new StringBuilder(); Log.d(TAG, userCommand); com.vectras.vm.logger.VectrasStatus.logError("VTERM: >" + userCommand + ""); + + // Show ProgressDialog + ProgressDialog progressDialog = new ProgressDialog(dialogActivity); + progressDialog.setMessage("Executing command, please wait..."); + progressDialog.setCancelable(false); + progressDialog.show(); + + new Thread(() -> { + try { + ProcessBuilder processBuilder = new ProcessBuilder(); + + String filesDir = Objects.requireNonNull(context.getFilesDir().getAbsolutePath()); + File tmpDir = new File(Objects.requireNonNull(context.getFilesDir()), "usr/tmp"); + + processBuilder.environment().put("PROOT_TMP_DIR", tmpDir.getAbsolutePath()); + processBuilder.environment().put("HOME", "/root"); + processBuilder.environment().put("USER", user); + processBuilder.environment().put("TERM", "xterm-256color"); + processBuilder.environment().put("TMPDIR", tmpDir.getAbsolutePath()); + processBuilder.environment().put("SHELL", "/bin/sh"); + processBuilder.environment().put("DISPLAY", DISPLAY); + processBuilder.environment().put("PULSE_SERVER", "127.0.0.1"); + + String[] prootCommand = { + TermuxService.PREFIX_PATH + "/bin/proot", + "--kill-on-exit", + "--link2symlink", + "-0", + "-r", filesDir + "/distro", + "-b", "/dev", + "-b", "/proc", + "-b", "/sys", + "-b", "/data/data/com.vectras.vm/files/distro/root:/dev/shm", + "-b", "/sdcard", + "-b", "/storage", + "-b", "/data", + "-b", "/data/data/com.vectras.vm/files/usr/tmp:/tmp", + "-w", "/root", + "/bin/sh", + "--login" + }; + + processBuilder.command(prootCommand); + qemuProcess = processBuilder.start(); + + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(qemuProcess.getOutputStream())); + BufferedReader reader = new BufferedReader(new InputStreamReader(qemuProcess.getInputStream())); + BufferedReader errorReader = new BufferedReader(new InputStreamReader(qemuProcess.getErrorStream())); + + writer.write(userCommand); + writer.newLine(); + writer.flush(); + writer.close(); + + String line; + while ((line = reader.readLine()) != null) { + Log.d(TAG, line); + com.vectras.vm.logger.VectrasStatus.logError("VTERM: >" + line + ""); + output.append(line).append("\n"); + } + + while ((line = errorReader.readLine()) != null) { + Log.w(TAG, line); + com.vectras.vm.logger.VectrasStatus.logError("VTERM ERROR: >" + line + ""); + output.append(line).append("\n"); + } + + int exitCode = qemuProcess.waitFor(); + if (exitCode != 0) { + output.append("Execution finished with exit code: ").append(exitCode).append("\n"); + } + } catch (IOException | InterruptedException e) { + progressDialog.dismiss(); // Dismiss ProgressDialog + output.append(e.getMessage()); + errors.append(Log.getStackTraceString(e)); + } finally { + new Handler(Looper.getMainLooper()).post(() -> { + progressDialog.dismiss(); // Dismiss ProgressDialog + VectrasApp.TerminalOutput = output.toString(); + if (showResultDialog) { + String finalOutput = output.toString(); + String finalErrors = errors.toString(); + showDialog(finalOutput.isEmpty() ? finalErrors : finalOutput.replace("read interrupted", "Done!"), dialogActivity, userCommand); + } + }); + } + }).start(); + } + + public void executeShellCommand2(String userCommand, boolean showResultDialog, Activity dialogActivity) { + StringBuilder output = new StringBuilder(); + StringBuilder errors = new StringBuilder(); + Log.d(TAG, userCommand); + com.vectras.vm.logger.VectrasStatus.logError("VTERM: >" + userCommand + ""); new Thread(() -> { try { // Setup the qemuProcess builder to start PRoot with environmental variables and commands @@ -195,6 +293,212 @@ public class Terminal { }).start(); } + public void extractQemuVersion(String userCommand, boolean showResultDialog, Activity dialogActivity, CommandCallback callback) { + StringBuilder output = new StringBuilder(); + StringBuilder errors = new StringBuilder(); + Log.d(TAG, userCommand); + com.vectras.vm.logger.VectrasStatus.logError("VTERM: >" + userCommand + ""); + + new Thread(() -> { + try { + // Process setup (same as your original code) + ProcessBuilder processBuilder = new ProcessBuilder(); + + String filesDir = Objects.requireNonNull(context.getFilesDir().getAbsolutePath()); + File tmpDir = new File(Objects.requireNonNull(context.getFilesDir()), "usr/tmp"); + + processBuilder.environment().put("PROOT_TMP_DIR", tmpDir.getAbsolutePath()); + processBuilder.environment().put("HOME", "/root"); + processBuilder.environment().put("USER", user); + processBuilder.environment().put("TERM", "xterm-256color"); + processBuilder.environment().put("TMPDIR", tmpDir.getAbsolutePath()); + processBuilder.environment().put("SHELL", "/bin/sh"); + processBuilder.environment().put("DISPLAY", DISPLAY); + processBuilder.environment().put("PULSE_SERVER", "127.0.0.1"); + processBuilder.environment().put("XDG_RUNTIME_DIR", "${TMPDIR}"); + processBuilder.environment().put("SDL_VIDEODRIVER", "x11"); + + String[] prootCommand = { + TermuxService.PREFIX_PATH + "/bin/proot", + "--kill-on-exit", + "--link2symlink", + "-0", + "-r", filesDir + "/distro", + "-b", "/dev", + "-b", "/proc", + "-b", "/sys", + "-b", "/data/data/com.vectras.vm/files/distro/root:/dev/shm", + "-b", "/sdcard", + "-b", "/storage", + "-b", "/data", + "-b", "/data/data/com.vectras.vm/files/usr/tmp:/tmp", + "-w", "/root", + "/bin/sh", + "--login" + }; + + processBuilder.command(prootCommand); + qemuProcess = processBuilder.start(); + + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(qemuProcess.getOutputStream())); + BufferedReader reader = new BufferedReader(new InputStreamReader(qemuProcess.getInputStream())); + BufferedReader errorReader = new BufferedReader(new InputStreamReader(qemuProcess.getErrorStream())); + + writer.write(userCommand); + writer.newLine(); + writer.flush(); + writer.close(); + + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + + while ((line = errorReader.readLine()) != null) { + errors.append(line).append("\n"); + } + + reader.close(); + errorReader.close(); + + qemuProcess.waitFor(); + + } catch (IOException | InterruptedException e) { + output.append(e.getMessage()); + errors.append(Log.getStackTraceString(e)); + } finally { + String finalOutput = output.toString(); + String finalErrors = errors.toString(); + + // Extract version using regex + String version = extractVersion(finalOutput); + + // Run callback on main thread + new Handler(Looper.getMainLooper()).post(() -> { + if (callback != null) { + callback.onCommandCompleted(version != null ? version : finalOutput, finalErrors); + } + if (showResultDialog) { + showDialog(finalOutput.isEmpty() ? finalErrors : finalOutput, dialogActivity, userCommand); + } + }); + } + }).start(); + } + + private String extractVersion(String output) { + String regex = "QEMU emulator version ([\\d.]+)"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(output); + if (matcher.find()) { + return matcher.group(1); // Return the version number + } + return null; + } + + public interface CommandCallback { + void onCommandCompleted(String output, String errors); + } + + public String executeShellCommand(String userCommand, Activity dialogActivity, CommandCallback callback) { + StringBuilder output = new StringBuilder(); + StringBuilder errors = new StringBuilder(); + Log.d(TAG, userCommand); + com.vectras.vm.logger.VectrasStatus.logError("VTERM: >" + userCommand + ""); + + // Show ProgressDialog on the main thread + ProgressDialog progressDialog = new ProgressDialog(dialogActivity); + progressDialog.setMessage("Executing command, please wait..."); + progressDialog.setCancelable(false); + + // Make sure to show the dialog on the main thread + new Handler(Looper.getMainLooper()).post(() -> progressDialog.show()); + + new Thread(() -> { + try { + ProcessBuilder processBuilder = new ProcessBuilder(); + + String filesDir = Objects.requireNonNull(context.getFilesDir().getAbsolutePath()); + File tmpDir = new File(Objects.requireNonNull(context.getFilesDir()), "usr/tmp"); + + processBuilder.environment().put("PROOT_TMP_DIR", tmpDir.getAbsolutePath()); + processBuilder.environment().put("HOME", "/root"); + processBuilder.environment().put("USER", user); + processBuilder.environment().put("TERM", "xterm-256color"); + processBuilder.environment().put("TMPDIR", tmpDir.getAbsolutePath()); + processBuilder.environment().put("SHELL", "/bin/sh"); + processBuilder.environment().put("DISPLAY", DISPLAY); + processBuilder.environment().put("PULSE_SERVER", "127.0.0.1"); + + String[] prootCommand = { + TermuxService.PREFIX_PATH + "/bin/proot", + "--kill-on-exit", + "--link2symlink", + "-0", + "-r", filesDir + "/distro", + "-b", "/dev", + "-b", "/proc", + "-b", "/sys", + "-b", "/data/data/com.vectras.vm/files/distro/root:/dev/shm", + "-b", "/sdcard", + "-b", "/storage", + "-b", "/data", + "-b", "/data/data/com.vectras.vm/files/usr/tmp:/tmp", + "-w", "/root", + "/bin/sh", + "--login" + }; + + processBuilder.command(prootCommand); + qemuProcess = processBuilder.start(); + + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(qemuProcess.getOutputStream())); + BufferedReader reader = new BufferedReader(new InputStreamReader(qemuProcess.getInputStream())); + BufferedReader errorReader = new BufferedReader(new InputStreamReader(qemuProcess.getErrorStream())); + + writer.write(userCommand); + writer.newLine(); + writer.flush(); + writer.close(); + + String line; + while ((line = reader.readLine()) != null) { + Log.d(TAG, line); + com.vectras.vm.logger.VectrasStatus.logError("VTERM: >" + line + ""); + output.append(line).append("\n"); + } + + while ((line = errorReader.readLine()) != null) { + Log.w(TAG, line); + com.vectras.vm.logger.VectrasStatus.logError("VTERM ERROR: >" + line + ""); + errors.append(line).append("\n"); + } + + int exitCode = qemuProcess.waitFor(); + if (exitCode != 0) { + output.append("Execution finished with exit code: ").append(exitCode).append("\n"); + } + + } catch (IOException | InterruptedException e) { + output.append(e.getMessage()); + errors.append(Log.getStackTraceString(e)); + } finally { + // Dismiss ProgressDialog on the main thread + new Handler(Looper.getMainLooper()).post(() -> progressDialog.dismiss()); + + // Return the output and errors via callback on the main thread + String finalOutput = output.toString(); + String finalErrors = errors.toString(); + + // Use callback to return both output and errors + new Handler(Looper.getMainLooper()).post(() -> callback.onCommandCompleted(finalOutput, finalErrors)); + } + }).start(); + + return "Execution is in progress..."; // Returning a message indicating the command execution is ongoing + } + + private boolean checkInstallation() { String filesDir = context.getFilesDir().getAbsolutePath(); File distro = new File(filesDir, "distro"); @@ -215,4 +519,27 @@ public class Terminal { Log.d(TAG, "QEMU process was null."); } } + + /** + * Checks if a package is installed using `apk info`. + * + * @param packageName The name of the package to check. + * @return True if the package is installed, otherwise false. + */ + private static boolean isPackageInstalled(String packageName) { + try { + Process process = new ProcessBuilder("apk", "info", packageName).start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.equals(packageName)) { + return true; + } + } + process.waitFor(); + } catch (Exception e) { + Log.e(TAG, "Error checking package: " + packageName, e); + } + return false; + } } diff --git a/app/src/main/res/layout/content_about.xml b/app/src/main/res/layout/content_about.xml index bd4ff8a..12eb2fe 100644 --- a/app/src/main/res/layout/content_about.xml +++ b/app/src/main/res/layout/content_about.xml @@ -23,8 +23,8 @@ + android:gravity="center" + android:orientation="vertical"> @@ -91,6 +91,29 @@ + + + + + + + @@ -107,80 +130,87 @@ - + + + - - - -