Add the TermuxSession class for linking a TerminalSession to an ExecutionCommand.

TermuxSession will maintain info for foreground Termux sessions. Each terminal session started by TermuxService will now be linked to a ExectionCommand that started it.

This also fixes bugs where newly created session in some cases were not being automatically selected and scrolled to, like when adding a named or failsafe session or those which were created for executable intents.
This commit is contained in:
agnostic-apollo
2021-03-25 01:18:13 +05:00
parent dff2794501
commit 78a99fddfd
6 changed files with 360 additions and 193 deletions

View File

@@ -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) {

View File

@@ -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<TerminalSession> mTerminalSessions = new ArrayList<>();
final List<TermuxSession> 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<TermuxSession> 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<TerminalSession> getSessions() {
return mTerminalSessions;
}
synchronized public static int getNextExecutionId() {
return EXECUTION_ID++;
}
}

View File

@@ -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;
}
}

View File

@@ -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<TerminalSession> 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<TerminalSession> 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]);

View File

@@ -24,14 +24,14 @@ import com.termux.terminal.TerminalSession;
import java.util.List;
public class TermuxSessionsListViewController extends ArrayAdapter<TerminalSession> implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
public class TermuxSessionsListViewController extends ArrayAdapter<TermuxSession> 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<TerminalSession> sessionList) {
public TermuxSessionsListViewController(TermuxActivity activity, List<TermuxSession> sessionList) {
super(activity.getApplicationContext(), R.layout.item_terminal_sessions_list, sessionList);
this.mActivity = activity;
}
@@ -48,7 +48,7 @@ public class TermuxSessionsListViewController extends ArrayAdapter<TerminalSessi
TextView sessionTitleView = sessionRowView.findViewById(R.id.session_title);
TerminalSession sessionAtRow = getItem(position);
TerminalSession sessionAtRow = getItem(position).getTerminalSession();
if (sessionAtRow == null) {
sessionTitleView.setText("null session");
return sessionRowView;
@@ -91,16 +91,16 @@ public class TermuxSessionsListViewController extends ArrayAdapter<TerminalSessi
@Override
public void onItemClick(AdapterView<?> 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;
}

View File

@@ -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;
}