diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index dd6f5058..3f60c0df 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -214,7 +214,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection // Get the session stored in shared preferences stored by {@link #onStop} if its valid, // otherwise get the last session currently running. mTermuxSessionClient.setCurrentSession(mTermuxSessionClient.getCurrentStoredSessionOrLast()); - terminalSessionListNotifyUpdated(); + termuxSessionListNotifyUpdated(); } registerReceiver(mTermuxActivityBroadcastReceiever, new IntentFilter(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE)); @@ -242,7 +242,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection setTermuxSessionsListView(); - if (mTermuxService.getSessions().isEmpty()) { + if (mTermuxService.isTermuxSessionsEmpty()) { if (mIsVisible) { TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> { if (mTermuxService == null) return; // Activity might have been destroyed. @@ -395,9 +395,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection View newSessionButton = findViewById(R.id.new_session_button); newSessionButton.setOnClickListener(v -> mTermuxSessionClient.addNewSession(false, null)); newSessionButton.setOnLongClickListener(v -> { - DialogUtils.textInput(TermuxActivity.this, R.string.title_create_named_session, null, R.string.action_create_named_session_confirm, - text -> mTermuxSessionClient.addNewSession(false, text), R.string.action_new_session_failsafe, text -> mTermuxSessionClient.addNewSession(true, text) - , -1, null, null); + DialogUtils.textInput(TermuxActivity.this, R.string.title_create_named_session, null, + R.string.action_create_named_session_confirm, text -> mTermuxSessionClient.addNewSession(false, text), + R.string.action_new_session_failsafe, text -> mTermuxSessionClient.addNewSession(true, text), + -1, null, null); return true; }); } @@ -439,7 +440,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection private void setTermuxSessionsListView() { ListView termuxSessionsListView = findViewById(R.id.terminal_sessions_list); - mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getSessions()); + mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getTermuxSessions()); termuxSessionsListView.setAdapter(mTermuxSessionListViewController); termuxSessionsListView.setOnItemClickListener(mTermuxSessionListViewController); termuxSessionsListView.setOnItemLongClickListener(mTermuxSessionListViewController); @@ -468,6 +469,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection /** Show a toast and dismiss the last one if still visible. */ public void showToast(String text, boolean longDuration) { + if(text == null || text.isEmpty()) return; if (mLastToast != null) mLastToast.cancel(); mLastToast = Toast.makeText(TermuxActivity.this, text, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); mLastToast.setGravity(Gravity.TOP, 0, 0); @@ -642,7 +644,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection return (DrawerLayout) findViewById(R.id.drawer_layout); } - public void terminalSessionListNotifyUpdated() { + public void termuxSessionListNotifyUpdated() { mTermuxSessionListViewController.notifyDataSetChanged(); } @@ -683,6 +685,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection + public static void updateTermuxActivityStyling(Context context) { + // Make sure that terminal styling is always applied. + Intent stylingIntent = new Intent(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE); + stylingIntent.putExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE, "styling"); + context.sendBroadcast(stylingIntent); + } + class TermuxActivityBroadcastReceiever extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 01feb005..379bcdaf 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -24,9 +24,11 @@ import com.termux.R; import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY; import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_SERVICE; import com.termux.app.settings.preferences.TermuxAppSharedPreferences; +import com.termux.app.terminal.TermuxSession; import com.termux.app.terminal.TermuxSessionClient; import com.termux.app.terminal.TermuxSessionClientBase; import com.termux.app.utils.Logger; +import com.termux.app.utils.ShellUtils; import com.termux.app.utils.TextDataUtils; import com.termux.models.ExecutionCommand; import com.termux.models.ExecutionCommand.ExecutionState; @@ -38,8 +40,10 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; + /** - * A service holding a list of terminal sessions, {@link #mTerminalSessions}, showing a foreground notification while + * A service holding a list of termux sessions, {@link #mTermuxSessions}, showing a foreground notification while * running so that it is not terminated. The user interacts with the session through {@link TermuxActivity}, but this * service may outlive the activity when the user or the system disposes of the activity. In that case the user may * restart {@link TermuxActivity} later to yet again access the sessions. @@ -67,12 +71,12 @@ public final class TermuxService extends Service { private final Handler mHandler = new Handler(); /** - * The terminal sessions which this service manages. + * The termux sessions which this service manages. * Note that this list is observed by {@link TermuxActivity#mTermuxSessionListViewController}, * so any changes must be made on the UI thread and followed by a call to * {@link ArrayAdapter#notifyDataSetChanged()} }. */ - final List mTerminalSessions = new ArrayList<>(); + final List mTermuxSessions = new ArrayList<>(); /** * The background jobs which this service manages. @@ -160,7 +164,7 @@ public final class TermuxService extends Service { } actionReleaseWakeLock(false); - finishAllTerminalSessions(); + finishAllTermuxSessions(); runStopForeground(); } @@ -205,7 +209,7 @@ public final class TermuxService extends Service { /** Process action to stop service. */ private void actionStopService() { mWantsToStop = true; - finishAllTerminalSessions(); + finishAllTermuxSessions(); requestStopService(); } @@ -273,7 +277,8 @@ public final class TermuxService extends Service { Logger.logDebug(LOG_TAG, "WakeLocks released successfully"); } - /** Process action to execute a shell command in a foreground session or in background. */ + /** Process {@link TERMUX_SERVICE#ACTION_SERVICE_EXECUTE} intent to execute a shell command in + * a foreground termux session or in background. */ private void actionServiceExecute(Intent intent) { if (intent == null){ Logger.logError(LOG_TAG, "Ignoring null intent to actionServiceExecute"); @@ -303,15 +308,18 @@ public final class TermuxService extends Service { if (executionCommand.inBackground) { executeBackgroundCommand(executionCommand); } else { - executeForegroundCommand(executionCommand); + executeTermuxSessionCommand(executionCommand); } } /** Execute a shell command in background with {@link BackgroundJob}. */ private void executeBackgroundCommand(ExecutionCommand executionCommand) { + if (executionCommand == null) return; + Logger.logDebug(LOG_TAG, "Starting background command"); - Logger.logDebug(LOG_TAG, executionCommand.toString()); + if(Logger.getLogLevel() >= Logger.LOG_LEVEL_VERBOSE) + Logger.logVerbose(LOG_TAG, executionCommand.toString()); BackgroundJob task = new BackgroundJob(executionCommand, this); @@ -328,65 +336,134 @@ public final class TermuxService extends Service { } /** Execute a shell command in a foreground terminal session. */ - private void executeForegroundCommand(ExecutionCommand executionCommand) { - Logger.logDebug(LOG_TAG, "Starting foreground command"); + private void executeTermuxSessionCommand(ExecutionCommand executionCommand) { + if (executionCommand == null) return; + + Logger.logDebug(LOG_TAG, "Starting foreground termux session command"); - if(!executionCommand.setState(ExecutionState.EXECUTING)) - return; - - Logger.logDebug(LOG_TAG, executionCommand.toString()); - - TerminalSession newSession = createTerminalSession(executionCommand.executable, executionCommand.arguments, executionCommand.workingDirectory, executionCommand.isFailsafe); + String sessionName = null; // Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh". if (executionCommand.executable != null) { - int lastSlash = executionCommand.executable.lastIndexOf('/'); - String name = (lastSlash == -1) ? executionCommand.executable : executionCommand.executable.substring(lastSlash + 1); - name = name.replace('-', ' '); - newSession.mSessionName = name; + sessionName = ShellUtils.getExecutableBasename(executionCommand.executable).replace('-', ' '); } + TermuxSession newTermuxSession = createTermuxSession(executionCommand, sessionName); + if (newTermuxSession == null) return; + + handleSessionAction(TextDataUtils.getIntFromString(executionCommand.sessionAction, + TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_SWITCH_TO_NEW_SESSION_AND_OPEN_ACTIVITY), + newTermuxSession.getTerminalSession()); + } + + /** + * Create a termux session. + * Currently called by {@link TermuxSessionClient#addNewSession(boolean, String)} to add a new termux session. + */ + @Nullable + public TermuxSession createTermuxSession(String executablePath, String[] arguments, String workingDirectory, boolean isFailSafe, String sessionName) { + return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, false, isFailSafe), sessionName); + } + + /** Create a termux session. */ + @Nullable + public synchronized TermuxSession createTermuxSession(ExecutionCommand executionCommand, String sessionName) { + if (executionCommand == null) return null; + + Logger.logDebug(LOG_TAG, "Creating termux session"); + + if (executionCommand.inBackground) { + Logger.logDebug(LOG_TAG, "Ignoring a background execution command passed to createTermuxSession()"); + return null; + } + + if(Logger.getLogLevel() >= Logger.LOG_LEVEL_VERBOSE) + Logger.logVerbose(LOG_TAG, executionCommand.toString()); + + TermuxSession newTermuxSession = TermuxSession.create(executionCommand, getTermuxSessionClient(), sessionName); + if (newTermuxSession == null) { + Logger.logError(LOG_TAG, "Failed to execute new termux session command for:\n" + executionCommand.toString()); + return null; + }; + + mTermuxSessions.add(newTermuxSession); + // Notify {@link TermuxSessionsListViewController} that sessions list has been updated if // activity in is foreground if(mTermuxSessionClient != null) - mTermuxSessionClient.terminalSessionListNotifyUpdated(); + mTermuxSessionClient.termuxSessionListNotifyUpdated(); - handleSessionAction(TextDataUtils.getIntFromString(executionCommand.sessionAction, TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_SWITCH_TO_NEW_SESSION_AND_OPEN_ACTIVITY), newSession); + updateNotification(); + TermuxActivity.updateTermuxActivityStyling(this); + + return newTermuxSession; } - private void setCurrentStoredSession(TerminalSession newSession) { - // Make the newly created session the current one to be displayed: - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(this); - preferences.setCurrentSession(newSession.mHandle); + /** Remove a termux session. */ + public synchronized int removeTermuxSession(TerminalSession sessionToRemove) { + int index = getIndexOfSession(sessionToRemove); + + if(index >= 0) { + TermuxSession termuxSession = mTermuxSessions.get(index); + + if (termuxSession.getExecutionCommand().setState(ExecutionState.EXECUTED)) { + ; + } + + mTermuxSessions.remove(termuxSession); + + // Notify {@link TermuxSessionsListViewController} that sessions list has been updated if + // activity in is foreground + if(mTermuxSessionClient != null) + mTermuxSessionClient.termuxSessionListNotifyUpdated(); + } + + if (mTermuxSessions.isEmpty() && mWakeLock == null) { + // Finish if there are no sessions left and the wake lock is not held, otherwise keep the service alive if + // holding wake lock since there may be daemon processes (e.g. sshd) running. + requestStopService(); + } else { + updateNotification(); + } + return index; } + /** Finish all termux sessions by sending SIGKILL to their shells. */ + private synchronized void finishAllTermuxSessions() { + for (int i = 0; i < mTermuxSessions.size(); i++) + mTermuxSessions.get(i).getTerminalSession().finishIfRunning(); + } + + + /** Process session action for new session. */ - private void handleSessionAction(int sessionAction, TerminalSession newSession) { - Logger.logDebug(LOG_TAG, "Processing sessionAction \"" + sessionAction + "\" for session \"" + newSession.mSessionName + "\""); + private void handleSessionAction(int sessionAction, TerminalSession newTerminalSession) { + Logger.logDebug(LOG_TAG, "Processing sessionAction \"" + sessionAction + "\" for session \"" + newTerminalSession.mSessionName + "\""); switch (sessionAction) { case TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_SWITCH_TO_NEW_SESSION_AND_OPEN_ACTIVITY: - setCurrentStoredSession(newSession); + setCurrentStoredTerminalSession(newTerminalSession); if(mTermuxSessionClient != null) - mTermuxSessionClient.setCurrentSession(newSession); + mTermuxSessionClient.setCurrentSession(newTerminalSession); startTermuxActivity(); break; case TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_KEEP_CURRENT_SESSION_AND_OPEN_ACTIVITY: - if(mTerminalSessions.size() == 1) - setCurrentStoredSession(newSession); + if(getTermuxSessionsSize() == 1) + setCurrentStoredTerminalSession(newTerminalSession); startTermuxActivity(); break; case TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_SWITCH_TO_NEW_SESSION_AND_DONT_OPEN_ACTIVITY: - setCurrentStoredSession(newSession); + setCurrentStoredTerminalSession(newTerminalSession); if(mTermuxSessionClient != null) - mTermuxSessionClient.setCurrentSession(newSession); + mTermuxSessionClient.setCurrentSession(newTerminalSession); break; case TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_KEEP_CURRENT_SESSION_AND_DONT_OPEN_ACTIVITY: - if(mTerminalSessions.size() == 1) - setCurrentStoredSession(newSession); + if(getTermuxSessionsSize() == 1) + setCurrentStoredTerminalSession(newTerminalSession); break; default: - Logger.logError(LOG_TAG, "Invalid sessionAction: \"" + sessionAction + "\""); + Logger.logError(LOG_TAG, "Invalid sessionAction: \"" + sessionAction + "\". Force using default sessionAction."); + handleSessionAction(TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION_SWITCH_TO_NEW_SESSION_AND_OPEN_ACTIVITY, newTerminalSession); break; } } @@ -396,78 +473,6 @@ public final class TermuxService extends Service { startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } - /** Create a terminal session. */ - public TerminalSession createTerminalSession(String executablePath, String[] arguments, String workingDirectory, boolean isFailSafe) { - TermuxConstants.TERMUX_HOME_DIR.mkdirs(); - - if (workingDirectory == null || workingDirectory.isEmpty()) workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH; - - String[] env = BackgroundJob.buildEnvironment(isFailSafe, workingDirectory); - boolean isLoginShell = false; - - if (executablePath == null) { - if (!isFailSafe) { - for (String shellBinary : new String[]{"login", "bash", "zsh"}) { - File shellFile = new File(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH, shellBinary); - if (shellFile.canExecute()) { - executablePath = shellFile.getAbsolutePath(); - break; - } - } - } - - if (executablePath == null) { - // Fall back to system shell as last resort: - executablePath = "/system/bin/sh"; - } - isLoginShell = true; - } - - String[] processArgs = BackgroundJob.setupProcessArgs(executablePath, arguments); - executablePath = processArgs[0]; - int lastSlashIndex = executablePath.lastIndexOf('/'); - String processName = (isLoginShell ? "-" : "") + - (lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1)); - - String[] args = new String[processArgs.length]; - args[0] = processName; - if (processArgs.length > 1) System.arraycopy(processArgs, 1, args, 1, processArgs.length - 1); - - TerminalSession session = new TerminalSession(executablePath, workingDirectory, args, env, getTermuxSessionClient()); - mTerminalSessions.add(session); - updateNotification(); - - // Make sure that terminal styling is always applied. - Intent stylingIntent = new Intent(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE); - stylingIntent.putExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE, "styling"); - sendBroadcast(stylingIntent); - - return session; - } - - /** Remove a terminal session. */ - public int removeTerminalSession(TerminalSession sessionToRemove) { - int indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove); - mTerminalSessions.remove(indexOfRemoved); - - if (mTerminalSessions.isEmpty() && mWakeLock == null) { - // Finish if there are no sessions left and the wake lock is not held, otherwise keep the service alive if - // holding wake lock since there may be daemon processes (e.g. sshd) running. - requestStopService(); - } else { - updateNotification(); - } - return indexOfRemoved; - } - - /** Finish all terminal sessions by sending SIGKILL to their shells. */ - private void finishAllTerminalSessions() { - for (int i = 0; i < mTerminalSessions.size(); i++) - mTerminalSessions.get(i).finishIfRunning(); - } - - - /** If {@link TermuxActivity} has not bound to the {@link TermuxService} yet or is destroyed, then * interface functions requiring the activity should not be available to the terminal sessions, * so we just return the {@link #mTermuxSessionClientBase}. Once {@link TermuxActivity} bind @@ -479,7 +484,7 @@ public final class TermuxService extends Service { * @return Returns the {@link TermuxSessionClient} if {@link TermuxActivity} has bound with * {@link TermuxService}, otherwise {@link TermuxSessionClientBase}. */ - public TermuxSessionClientBase getTermuxSessionClient() { + public synchronized TermuxSessionClientBase getTermuxSessionClient() { if (mTermuxSessionClient != null) return mTermuxSessionClient; else @@ -494,20 +499,20 @@ public final class TermuxService extends Service { * @param termuxSessionClient The {@link TermuxSessionClient} object that fully * implements the {@link TerminalSessionClient} interface. */ - public void setTermuxSessionClient(TermuxSessionClient termuxSessionClient) { + public synchronized void setTermuxSessionClient(TermuxSessionClient termuxSessionClient) { mTermuxSessionClient = termuxSessionClient; - for (int i = 0; i < mTerminalSessions.size(); i++) - mTerminalSessions.get(i).updateTerminalSessionClient(mTermuxSessionClient); + for (int i = 0; i < mTermuxSessions.size(); i++) + mTermuxSessions.get(i).getTerminalSession().updateTerminalSessionClient(mTermuxSessionClient); } /** This should be called when {@link TermuxActivity} has been destroyed and in {@link #onUnbind(Intent)} * so that the {@link TermuxService} and {@link TerminalSession} and {@link TerminalEmulator} * clients do not hold an activity references. */ - public void unsetTermuxSessionClient() { - for (int i = 0; i < mTerminalSessions.size(); i++) - mTerminalSessions.get(i).updateTerminalSessionClient(mTermuxSessionClientBase); + public synchronized void unsetTermuxSessionClient() { + for (int i = 0; i < mTermuxSessions.size(); i++) + mTermuxSessions.get(i).getTerminalSession().updateTerminalSessionClient(mTermuxSessionClientBase); mTermuxSessionClient = null; } @@ -521,7 +526,7 @@ public final class TermuxService extends Service { notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0); - int sessionCount = mTerminalSessions.size(); + int sessionCount = getTermuxSessionsSize(); int taskCount = mBackgroundTasks.size(); String contentText = sessionCount + " session" + (sessionCount == 1 ? "" : "s"); if (taskCount > 0) { @@ -582,7 +587,7 @@ public final class TermuxService extends Service { /** Update the shown foreground service notification after making any changes that affect it. */ void updateNotification() { - if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) { + if (mWakeLock == null && mTermuxSessions.isEmpty() && mBackgroundTasks.isEmpty()) { // Exit if we are updating after the user disabled all locks with no sessions or tasks running. requestStopService(); } else { @@ -592,16 +597,63 @@ public final class TermuxService extends Service { + private void setCurrentStoredTerminalSession(TerminalSession session) { + if(session == null) return; + // Make the newly created session the current one to be displayed: + TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(this); + preferences.setCurrentSession(session.mHandle); + } + + public synchronized boolean isTermuxSessionsEmpty() { + return mTermuxSessions.isEmpty(); + } + + public synchronized int getTermuxSessionsSize() { + return mTermuxSessions.size(); + } + + public synchronized List getTermuxSessions() { + return mTermuxSessions; + } + + @Nullable + public synchronized TermuxSession getTermuxSession(int index) { + if(index >= 0 && index < mTermuxSessions.size()) + return mTermuxSessions.get(index); + else + return null; + } + + public synchronized TermuxSession getLastTermuxSession() { + return mTermuxSessions.isEmpty() ? null : mTermuxSessions.get(mTermuxSessions.size() - 1); + } + + public synchronized int getIndexOfSession(TerminalSession terminalSession) { + for (int i = 0; i < mTermuxSessions.size(); i++) { + if (mTermuxSessions.get(i).getTerminalSession().equals(terminalSession)) + return i; + } + return -1; + } + + public synchronized TerminalSession getTerminalSessionForHandle(String sessionHandle) { + TerminalSession terminalSession; + for (int i = 0, len = mTermuxSessions.size(); i < len; i++) { + terminalSession = mTermuxSessions.get(i).getTerminalSession(); + if (terminalSession.mHandle.equals(sessionHandle)) + return terminalSession; + } + return null; + } + + + + public static synchronized int getNextExecutionId() { + return EXECUTION_ID++; + } + public boolean wantsToStop() { return mWantsToStop; } - public List getSessions() { - return mTerminalSessions; - } - - synchronized public static int getNextExecutionId() { - return EXECUTION_ID++; - } - } diff --git a/app/src/main/java/com/termux/app/terminal/TermuxSession.java b/app/src/main/java/com/termux/app/terminal/TermuxSession.java new file mode 100644 index 00000000..3af9f73d --- /dev/null +++ b/app/src/main/java/com/termux/app/terminal/TermuxSession.java @@ -0,0 +1,87 @@ +package com.termux.app.terminal; + +import com.termux.app.TermuxConstants; +import com.termux.app.utils.Logger; +import com.termux.app.utils.ShellUtils; +import com.termux.models.ExecutionCommand; +import com.termux.terminal.TerminalSession; + +import java.io.File; + +/** + * A class that maintains info for foreground Termux sessions. + * It also provides a way to link each {@link TerminalSession} with the {@link ExecutionCommand} + * that started it. + */ +public class TermuxSession { + + private final TerminalSession mTerminalSession; + private final ExecutionCommand mExecutionCommand; + + private static final String LOG_TAG = "TermuxSession"; + + private TermuxSession(TerminalSession terminalSession, ExecutionCommand executionCommand) { + this.mTerminalSession = terminalSession; + this.mExecutionCommand = executionCommand; + } + + public static TermuxSession create(ExecutionCommand executionCommand, TermuxSessionClientBase termuxSessionClient, String sessionName) { + TermuxConstants.TERMUX_HOME_DIR.mkdirs(); + + if (executionCommand.workingDirectory == null || executionCommand.workingDirectory.isEmpty()) executionCommand.workingDirectory = TermuxConstants.TERMUX_HOME_DIR_PATH; + + String[] environment = ShellUtils.buildEnvironment(executionCommand.isFailsafe, executionCommand.workingDirectory); + boolean isLoginShell = false; + + if (executionCommand.executable == null) { + if (!executionCommand.isFailsafe) { + for (String shellBinary : new String[]{"login", "bash", "zsh"}) { + File shellFile = new File(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH, shellBinary); + if (shellFile.canExecute()) { + executionCommand.executable = shellFile.getAbsolutePath(); + break; + } + } + } + + if (executionCommand.executable == null) { + // Fall back to system shell as last resort: + executionCommand.executable = "/system/bin/sh"; + } + isLoginShell = true; + } + + String[] processArgs = ShellUtils.setupProcessArgs(executionCommand.executable, executionCommand.arguments); + + executionCommand.executable = processArgs[0]; + String processName = (isLoginShell ? "-" : "") + ShellUtils.getExecutableBasename(executionCommand.executable); + + String[] arguments = new String[processArgs.length]; + arguments[0] = processName; + if (processArgs.length > 1) System.arraycopy(processArgs, 1, arguments, 1, processArgs.length - 1); + + executionCommand.arguments = arguments; + + if(!executionCommand.setState(ExecutionCommand.ExecutionState.EXECUTING)) + return null; + + Logger.logDebug(LOG_TAG, executionCommand.toString()); + + TerminalSession terminalSession = new TerminalSession(executionCommand.executable, executionCommand.workingDirectory, executionCommand.arguments, environment, termuxSessionClient); + + if (sessionName != null) { + terminalSession.mSessionName = sessionName; + } + + return new TermuxSession(terminalSession, executionCommand); + } + + public TerminalSession getTerminalSession() { + return mTerminalSession; + } + + public ExecutionCommand getExecutionCommand() { + return mExecutionCommand; + } + +} diff --git a/app/src/main/java/com/termux/app/terminal/TermuxSessionClient.java b/app/src/main/java/com/termux/app/terminal/TermuxSessionClient.java index c3e4df7f..8901e1f4 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxSessionClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxSessionClient.java @@ -27,7 +27,6 @@ import com.termux.terminal.TextStyle; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.util.List; import java.util.Properties; public class TermuxSessionClient extends TermuxSessionClientBase { @@ -68,7 +67,7 @@ public class TermuxSessionClient extends TermuxSessionClientBase { mActivity.showToast(toToastTitle(updatedSession), true); } - terminalSessionListNotifyUpdated(); + termuxSessionListNotifyUpdated(); } @Override @@ -81,7 +80,7 @@ public class TermuxSessionClient extends TermuxSessionClientBase { if (mActivity.isVisible() && finishedSession != mActivity.getCurrentSession()) { // Show toast for non-current sessions that exit. - int indexOfSession = mActivity.getTermuxService().getSessions().indexOf(finishedSession); + int indexOfSession = mActivity.getTermuxService().getIndexOfSession(finishedSession); // Verify that session was not removed before we got told about it finishing: if (indexOfSession >= 0) mActivity.showToast(toToastTitle(finishedSession) + " - exited", true); @@ -90,7 +89,7 @@ public class TermuxSessionClient extends TermuxSessionClientBase { if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { // On Android TV devices we need to use older behaviour because we may // not be able to have multiple launcher icons. - if (mActivity.getTermuxService().getSessions().size() > 1) { + if (mActivity.getTermuxService().getTermuxSessionsSize() > 1) { removeFinishedSession(finishedSession); } } else { @@ -100,8 +99,6 @@ public class TermuxSessionClient extends TermuxSessionClientBase { removeFinishedSession(finishedSession); } } - - terminalSessionListNotifyUpdated(); } @Override @@ -138,38 +135,49 @@ public class TermuxSessionClient extends TermuxSessionClientBase { - /** Try switching to session and note about it, but do nothing if already displaying the session. */ + /** Try switching to session. */ public void setCurrentSession(TerminalSession session) { + if(session == null) return; + if (mActivity.getTerminalView().attachSession(session)) { - noteSessionInfo(); - updateBackgroundColor(); + // notify about switched session if not already displaying the session + notifyOfSessionChange(); } + + // We call the following even when the session is already being displayed since config may + // be stale, like current session not selected or scrolled to. + checkAndScrollToSession(session); + updateBackgroundColor(); } - void noteSessionInfo() { + void notifyOfSessionChange() { if (!mActivity.isVisible()) return; TerminalSession session = mActivity.getCurrentSession(); - final int indexOfSession = mActivity.getTermuxService().getSessions().indexOf(session); mActivity.showToast(toToastTitle(session), false); - terminalSessionListNotifyUpdated(); - - final ListView termuxSessionsListView = mActivity.findViewById(R.id.terminal_sessions_list); - termuxSessionsListView.setItemChecked(indexOfSession, true); - termuxSessionsListView.smoothScrollToPosition(indexOfSession); } public void switchToSession(boolean forward) { TermuxService service = mActivity.getTermuxService(); - TerminalSession currentSession = mActivity.getCurrentSession(); - int index = service.getSessions().indexOf(currentSession); + TerminalSession currentTerminalSession = mActivity.getCurrentSession(); + int index = service.getIndexOfSession(currentTerminalSession); + int size = service.getTermuxSessionsSize(); if (forward) { - if (++index >= service.getSessions().size()) index = 0; + if (++index >= size) index = 0; } else { - if (--index < 0) index = service.getSessions().size() - 1; + if (--index < 0) index = size - 1; } - setCurrentSession(service.getSessions().get(index)); + + TermuxSession termuxSession = service.getTermuxSession(index); + if(termuxSession != null) + setCurrentSession(termuxSession.getTerminalSession()); + } + + public void switchToSession(int index) { + TermuxSession termuxSession = mActivity.getTermuxService().getTermuxSession(index); + if(termuxSession != null) + setCurrentSession(termuxSession.getTerminalSession()); } @SuppressLint("InflateParams") @@ -178,12 +186,12 @@ public class TermuxSessionClient extends TermuxSessionClientBase { DialogUtils.textInput(mActivity, R.string.title_rename_session, sessionToRename.mSessionName, R.string.action_rename_session_confirm, text -> { sessionToRename.mSessionName = text; - terminalSessionListNotifyUpdated(); + termuxSessionListNotifyUpdated(); }, -1, null, -1, null, null); } public void addNewSession(boolean isFailSafe, String sessionName) { - if (mActivity.getTermuxService().getSessions().size() >= MAX_SESSIONS) { + if (mActivity.getTermuxService().getTermuxSessionsSize() >= MAX_SESSIONS) { new AlertDialog.Builder(mActivity).setTitle(R.string.title_max_terminals_reached).setMessage(R.string.msg_max_terminals_reached) .setPositiveButton(android.R.string.ok, null).show(); } else { @@ -196,11 +204,12 @@ public class TermuxSessionClient extends TermuxSessionClientBase { workingDirectory = currentSession.getCwd(); } - TerminalSession newSession = mActivity.getTermuxService().createTerminalSession(null, null, workingDirectory, isFailSafe); - if (sessionName != null) { - newSession.mSessionName = sessionName; - } - setCurrentSession(newSession); + TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, workingDirectory, isFailSafe, sessionName); + if (newTermuxSession == null) return; + + TerminalSession newTerminalSession = newTermuxSession.getTerminalSession(); + setCurrentSession(newTerminalSession); + mActivity.getDrawer().closeDrawers(); } } @@ -222,8 +231,11 @@ public class TermuxSessionClient extends TermuxSessionClientBase { return stored; } else { // Else return the last session currently running - List sessions = mActivity.getTermuxService().getSessions(); - return sessions.isEmpty() ? null : sessions.get(sessions.size() - 1); + TermuxSession termuxSession = mActivity.getTermuxService().getLastTermuxSession(); + if(termuxSession != null) + return termuxSession.getTerminalSession(); + else + return null; } } @@ -235,41 +247,50 @@ public class TermuxSessionClient extends TermuxSessionClientBase { return null; // Check if the session handle found matches one of the currently running sessions - List sessions = context.getTermuxService().getSessions(); - for (int i = 0, len = sessions.size(); i < len; i++) { - TerminalSession session = sessions.get(i); - if (session.mHandle.equals(sessionHandle)) - return session; - } - - return null; + return context.getTermuxService().getTerminalSessionForHandle(sessionHandle); } public void removeFinishedSession(TerminalSession finishedSession) { // Return pressed with finished session - remove it. TermuxService service = mActivity.getTermuxService(); - int index = service.removeTerminalSession(finishedSession); - terminalSessionListNotifyUpdated(); - if (mActivity.getTermuxService().getSessions().isEmpty()) { + int index = service.removeTermuxSession(finishedSession); + int size = mActivity.getTermuxService().getTermuxSessionsSize(); + if (size == 0) { // There are no sessions to show, so finish the activity. mActivity.finishActivityIfNotFinishing(); } else { - if (index >= service.getSessions().size()) { - index = service.getSessions().size() - 1; + if (index >= size) { + index = size - 1; } - setCurrentSession(service.getSessions().get(index)); + TermuxSession termuxSession = service.getTermuxSession(index); + if(termuxSession != null) + setCurrentSession(termuxSession.getTerminalSession()); } } - public void terminalSessionListNotifyUpdated() { - mActivity.terminalSessionListNotifyUpdated(); + public void termuxSessionListNotifyUpdated() { + mActivity.termuxSessionListNotifyUpdated(); + } + + public void checkAndScrollToSession(TerminalSession session) { + if (!mActivity.isVisible()) return; + final int indexOfSession = mActivity.getTermuxService().getIndexOfSession(session); + if (indexOfSession < 0) return; + final ListView termuxSessionsListView = mActivity.findViewById(R.id.terminal_sessions_list); + if(termuxSessionsListView == null) return; + + termuxSessionsListView.setItemChecked(indexOfSession, true); + // Delay is necessary otherwise sometimes scroll to newly added session does not happen + termuxSessionsListView.postDelayed(() -> termuxSessionsListView.smoothScrollToPosition(indexOfSession), 1000); + + Logger.logError("scrolled to " + indexOfSession); } - String toToastTitle(TerminalSession session) { - final int indexOfSession = mActivity.getTermuxService().getSessions().indexOf(session); + final int indexOfSession = mActivity.getTermuxService().getIndexOfSession(session); + if (indexOfSession < 0) return null; StringBuilder toastTitle = new StringBuilder("[" + (indexOfSession + 1) + "]"); if (!TextUtils.isEmpty(session.mSessionName)) { toastTitle.append(" ").append(session.mSessionName); @@ -311,6 +332,7 @@ public class TermuxSessionClient extends TermuxSessionClientBase { } public void updateBackgroundColor() { + if (!mActivity.isVisible()) return; TerminalSession session = mActivity.getCurrentSession(); if (session != null && session.getEmulator() != null) { mActivity.getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); diff --git a/app/src/main/java/com/termux/app/terminal/TermuxSessionsListViewController.java b/app/src/main/java/com/termux/app/terminal/TermuxSessionsListViewController.java index 9951b071..94ea9de5 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxSessionsListViewController.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxSessionsListViewController.java @@ -24,14 +24,14 @@ import com.termux.terminal.TerminalSession; import java.util.List; -public class TermuxSessionsListViewController extends ArrayAdapter implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener { +public class TermuxSessionsListViewController extends ArrayAdapter implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener { final TermuxActivity mActivity; final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD); final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC); - public TermuxSessionsListViewController(TermuxActivity activity, List sessionList) { + public TermuxSessionsListViewController(TermuxActivity activity, List sessionList) { super(activity.getApplicationContext(), R.layout.item_terminal_sessions_list, sessionList); this.mActivity = activity; } @@ -48,7 +48,7 @@ public class TermuxSessionsListViewController extends ArrayAdapter parent, View view, int position, long id) { - TerminalSession clickedSession = getItem(position); - mActivity.getTermuxSessionClient().setCurrentSession(clickedSession); + TermuxSession clickedSession = getItem(position); + mActivity.getTermuxSessionClient().setCurrentSession(clickedSession.getTerminalSession()); mActivity.getDrawer().closeDrawers(); } @Override public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - final TerminalSession selectedSession = getItem(position); - mActivity.getTermuxSessionClient().renameSession(selectedSession); + final TermuxSession selectedSession = getItem(position); + mActivity.getTermuxSessionClient().renameSession(selectedSession.getTerminalSession()); return true; } diff --git a/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java index 98d5d146..9bcf48d8 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java @@ -20,7 +20,6 @@ import android.widget.Toast; import com.termux.R; import com.termux.app.TermuxActivity; -import com.termux.app.TermuxService; import com.termux.app.terminal.io.KeyboardShortcut; import com.termux.app.terminal.io.extrakeys.ExtraKeysView; import com.termux.app.settings.properties.TermuxPropertyConstants; @@ -135,10 +134,8 @@ public class TermuxViewClient implements TerminalViewClient { } else if (unicodeChar == '-') { changeFontSize(false); } else if (unicodeChar >= '1' && unicodeChar <= '9') { - int num = unicodeChar - '1'; - TermuxService service = mActivity.getTermuxService(); - if (service.getSessions().size() > num) - mTermuxSessionClient.setCurrentSession(service.getSessions().get(num)); + int index = unicodeChar - '1'; + mTermuxSessionClient.switchToSession(index); } return true; }