diff --git a/app/src/main/java/com/winlator/XServerDisplayActivity.java b/app/src/main/java/com/winlator/XServerDisplayActivity.java index 1281d09e32..2b81e24908 100644 --- a/app/src/main/java/com/winlator/XServerDisplayActivity.java +++ b/app/src/main/java/com/winlator/XServerDisplayActivity.java @@ -346,6 +346,43 @@ private void exit() { AppUtils.restartApplication(this); } + private void showCrashDialog(int exitCode) { + String signalName = getSignalName(exitCode); + String exitInfo = "Exit code: " + exitCode + (signalName != null ? " (" + signalName + ")" : ""); + + ArrayList logLines = ProcessHelper.getCrashLogSnapshot(); + StringBuilder logText = new StringBuilder(); + logText.append(exitInfo).append("\n\n"); + if (logLines.isEmpty()) { + logText.append("No debug output captured.\nEnable Wine debug or Box86/64 logs in Settings for more detail."); + } else { + logText.append("Last ").append(logLines.size()).append(" lines of output:\n\n"); + for (String line : logLines) logText.append(line).append("\n"); + } + + ContentDialog dialog = new ContentDialog(this); + dialog.setTitle(getString(R.string.process_crashed)); + dialog.setCancelable(false); + dialog.setMessage(logText.toString()); + dialog.setOnConfirmCallback(this::exit); + dialog.show(); + } + + private static String getSignalName(int exitCode) { + if (exitCode < 128) return null; + switch (exitCode - 128) { + case 4: return "Illegal instruction"; + case 6: return "Aborted"; + case 7: return "Bus error"; + case 8: return "Floating point exception"; + case 9: return "Killed"; + case 11: return "Segmentation fault"; + case 13: return "Broken pipe"; + case 15: return "Terminated"; + default: return "Signal " + (exitCode - 128); + } + } + private void setupWineSystemFiles() { String appVersion = String.valueOf(AppUtils.getVersionCode(this)); String imgVersion = String.valueOf(imageFs.getVersion()); @@ -449,7 +486,13 @@ else if (audioDriver.equals("pulseaudio")) { } guestProgramLauncherComponent.setEnvVars(envVars); - guestProgramLauncherComponent.setTerminationCallback((status) -> exit()); + guestProgramLauncherComponent.setTerminationCallback((status) -> { + if (status != 0) { + runOnUiThread(() -> showCrashDialog(status)); + } else { + exit(); + } + }); environment.addComponent(guestProgramLauncherComponent); if (isGenerateWineprefix()) generateWineprefix(); diff --git a/app/src/main/java/com/winlator/core/ProcessHelper.java b/app/src/main/java/com/winlator/core/ProcessHelper.java index 31d237d2b2..dc6bc94f49 100644 --- a/app/src/main/java/com/winlator/core/ProcessHelper.java +++ b/app/src/main/java/com/winlator/core/ProcessHelper.java @@ -16,6 +16,8 @@ public abstract class ProcessHelper { private static final ArrayList> debugCallbacks = new ArrayList<>(); private static final byte SIGCONT = 18; private static final byte SIGSTOP = 19; + private static final int CRASH_LOG_BUFFER_SIZE = 200; + private static final ArrayList crashLogBuffer = new ArrayList<>(); public static void suspendProcess(int pid) { Process.sendSignal(pid, SIGSTOP); @@ -46,10 +48,8 @@ public static int exec(String command, String[] envp, File workingDir, Callback< pid = pidField.getInt(process); pidField.setAccessible(false); - if (!debugCallbacks.isEmpty()) { - createDebugThread(process.getInputStream()); - createDebugThread(process.getErrorStream()); - } + createDebugThread(process.getInputStream()); + createDebugThread(process.getErrorStream()); if (terminationCallback != null) createWaitForThread(process, terminationCallback); } @@ -63,6 +63,10 @@ private static void createDebugThread(final InputStream inputStream) { String line; while ((line = reader.readLine()) != null) { if (PRINT_DEBUG) System.out.println(line); + synchronized (crashLogBuffer) { + crashLogBuffer.add(line); + while (crashLogBuffer.size() > CRASH_LOG_BUFFER_SIZE) crashLogBuffer.remove(0); + } synchronized (debugCallbacks) { if (!debugCallbacks.isEmpty()) { for (Callback callback : debugCallbacks) callback.call(line); @@ -102,6 +106,18 @@ public static void removeDebugCallback(Callback callback) { } } + public static ArrayList getCrashLogSnapshot() { + synchronized (crashLogBuffer) { + return new ArrayList<>(crashLogBuffer); + } + } + + public static void clearCrashLog() { + synchronized (crashLogBuffer) { + crashLogBuffer.clear(); + } + } + public static String[] splitCommand(String command) { ArrayList result = new ArrayList<>(); boolean startedQuotes = false; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4879afecf5..7e66c37c89 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -225,4 +225,5 @@ Warning: Installing a new version of Wine is recommended for testing purposes only. Startup Selection Wine Debug Channel + Process Crashed \ No newline at end of file