mirror of https://github.com/termux/termux-app
341 lines
13 KiB
Java
341 lines
13 KiB
Java
package com.termux.app.terminal;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.AlertDialog;
|
|
import android.content.ClipData;
|
|
import android.content.ClipboardManager;
|
|
import android.content.Context;
|
|
import android.content.pm.PackageManager;
|
|
import android.graphics.Typeface;
|
|
import android.media.AudioAttributes;
|
|
import android.media.SoundPool;
|
|
import android.text.TextUtils;
|
|
import android.widget.ListView;
|
|
|
|
import com.termux.R;
|
|
import com.termux.shared.interact.DialogUtils;
|
|
import com.termux.app.TermuxActivity;
|
|
import com.termux.shared.termux.TermuxConstants;
|
|
import com.termux.app.TermuxService;
|
|
import com.termux.shared.settings.properties.TermuxPropertyConstants;
|
|
import com.termux.app.terminal.io.BellHandler;
|
|
import com.termux.shared.logger.Logger;
|
|
import com.termux.terminal.TerminalColors;
|
|
import com.termux.terminal.TerminalSession;
|
|
import com.termux.terminal.TextStyle;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.InputStream;
|
|
import java.util.Properties;
|
|
|
|
public class TermuxSessionClient extends TermuxSessionClientBase {
|
|
|
|
private final TermuxActivity mActivity;
|
|
|
|
private static final int MAX_SESSIONS = 8;
|
|
|
|
private final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
|
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
|
|
|
private final int mBellSoundId;
|
|
|
|
private static final String LOG_TAG = "TermuxSessionClient";
|
|
|
|
public TermuxSessionClient(TermuxActivity activity) {
|
|
this.mActivity = activity;
|
|
|
|
mBellSoundId = mBellSoundPool.load(activity, R.raw.bell, 1);
|
|
}
|
|
|
|
@Override
|
|
public void onTextChanged(TerminalSession changedSession) {
|
|
if (!mActivity.isVisible()) return;
|
|
|
|
if (mActivity.getCurrentSession() == changedSession) mActivity.getTerminalView().onScreenUpdated();
|
|
}
|
|
|
|
@Override
|
|
public void onTitleChanged(TerminalSession updatedSession) {
|
|
if (!mActivity.isVisible()) return;
|
|
|
|
if (updatedSession != mActivity.getCurrentSession()) {
|
|
// Only show toast for other sessions than the current one, since the user
|
|
// probably consciously caused the title change to change in the current session
|
|
// and don't want an annoying toast for that.
|
|
mActivity.showToast(toToastTitle(updatedSession), true);
|
|
}
|
|
|
|
termuxSessionListNotifyUpdated();
|
|
}
|
|
|
|
@Override
|
|
public void onSessionFinished(final TerminalSession finishedSession) {
|
|
if (mActivity.getTermuxService().wantsToStop()) {
|
|
// The service wants to stop as soon as possible.
|
|
mActivity.finishActivityIfNotFinishing();
|
|
return;
|
|
}
|
|
|
|
if (mActivity.isVisible() && finishedSession != mActivity.getCurrentSession()) {
|
|
// Show toast for non-current sessions that exit.
|
|
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);
|
|
}
|
|
|
|
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().getTermuxSessionsSize() > 1) {
|
|
removeFinishedSession(finishedSession);
|
|
}
|
|
} else {
|
|
// Once we have a separate launcher icon for the failsafe session, it
|
|
// should be safe to auto-close session on exit code '0' or '130'.
|
|
if (finishedSession.getExitStatus() == 0 || finishedSession.getExitStatus() == 130) {
|
|
removeFinishedSession(finishedSession);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClipboardText(TerminalSession session, String text) {
|
|
if (!mActivity.isVisible()) return;
|
|
|
|
ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
|
}
|
|
|
|
@Override
|
|
public void onBell(TerminalSession session) {
|
|
if (!mActivity.isVisible()) return;
|
|
|
|
switch (mActivity.getProperties().getBellBehaviour()) {
|
|
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_VIBRATE:
|
|
BellHandler.getInstance(mActivity).doBell();
|
|
break;
|
|
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_BEEP:
|
|
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
|
break;
|
|
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_IGNORE:
|
|
// Ignore the bell character.
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onColorsChanged(TerminalSession changedSession) {
|
|
if (mActivity.getCurrentSession() == changedSession)
|
|
updateBackgroundColor();
|
|
}
|
|
|
|
|
|
|
|
/** Try switching to session. */
|
|
public void setCurrentSession(TerminalSession session) {
|
|
if (session == null) return;
|
|
|
|
if (mActivity.getTerminalView().attachSession(session)) {
|
|
// 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 notifyOfSessionChange() {
|
|
if (!mActivity.isVisible()) return;
|
|
|
|
TerminalSession session = mActivity.getCurrentSession();
|
|
mActivity.showToast(toToastTitle(session), false);
|
|
}
|
|
|
|
public void switchToSession(boolean forward) {
|
|
TermuxService service = mActivity.getTermuxService();
|
|
|
|
TerminalSession currentTerminalSession = mActivity.getCurrentSession();
|
|
int index = service.getIndexOfSession(currentTerminalSession);
|
|
int size = service.getTermuxSessionsSize();
|
|
if (forward) {
|
|
if (++index >= size) index = 0;
|
|
} else {
|
|
if (--index < 0) index = size - 1;
|
|
}
|
|
|
|
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")
|
|
public void renameSession(final TerminalSession sessionToRename) {
|
|
if (sessionToRename == null) return;
|
|
|
|
DialogUtils.textInput(mActivity, R.string.title_rename_session, sessionToRename.mSessionName, R.string.action_rename_session_confirm, text -> {
|
|
sessionToRename.mSessionName = text;
|
|
termuxSessionListNotifyUpdated();
|
|
}, -1, null, -1, null, null);
|
|
}
|
|
|
|
public void addNewSession(boolean isFailSafe, String sessionName) {
|
|
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 {
|
|
TerminalSession currentSession = mActivity.getCurrentSession();
|
|
|
|
String workingDirectory;
|
|
if (currentSession == null) {
|
|
workingDirectory = mActivity.getProperties().getDefaultWorkingDirectory();
|
|
} else {
|
|
workingDirectory = currentSession.getCwd();
|
|
}
|
|
|
|
TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, workingDirectory, isFailSafe, sessionName);
|
|
if (newTermuxSession == null) return;
|
|
|
|
TerminalSession newTerminalSession = newTermuxSession.getTerminalSession();
|
|
setCurrentSession(newTerminalSession);
|
|
|
|
mActivity.getDrawer().closeDrawers();
|
|
}
|
|
}
|
|
|
|
public void setCurrentStoredSession() {
|
|
TerminalSession currentSession = mActivity.getCurrentSession();
|
|
if (currentSession != null)
|
|
mActivity.getPreferences().setCurrentSession(currentSession.mHandle);
|
|
else
|
|
mActivity.getPreferences().setCurrentSession(null);
|
|
}
|
|
|
|
/** The current session as stored or the last one if that does not exist. */
|
|
public TerminalSession getCurrentStoredSessionOrLast() {
|
|
TerminalSession stored = getCurrentStoredSession(mActivity);
|
|
|
|
if (stored != null) {
|
|
// If a stored session is in the list of currently running sessions, then return it
|
|
return stored;
|
|
} else {
|
|
// Else return the last session currently running
|
|
TermuxSession termuxSession = mActivity.getTermuxService().getLastTermuxSession();
|
|
if (termuxSession != null)
|
|
return termuxSession.getTerminalSession();
|
|
else
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private TerminalSession getCurrentStoredSession(TermuxActivity context) {
|
|
String sessionHandle = mActivity.getPreferences().getCurrentSession();
|
|
|
|
// If no session is stored in shared preferences
|
|
if (sessionHandle == null)
|
|
return null;
|
|
|
|
// Check if the session handle found matches one of the currently running sessions
|
|
return context.getTermuxService().getTerminalSessionForHandle(sessionHandle);
|
|
}
|
|
|
|
public void removeFinishedSession(TerminalSession finishedSession) {
|
|
// Return pressed with finished session - remove it.
|
|
TermuxService service = mActivity.getTermuxService();
|
|
|
|
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 >= size) {
|
|
index = size - 1;
|
|
}
|
|
TermuxSession termuxSession = service.getTermuxSession(index);
|
|
if (termuxSession != null)
|
|
setCurrentSession(termuxSession.getTerminalSession());
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
String toToastTitle(TerminalSession 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);
|
|
}
|
|
String title = session.getTitle();
|
|
if (!TextUtils.isEmpty(title)) {
|
|
// Space to "[${NR}] or newline after session name:
|
|
toastTitle.append(session.mSessionName == null ? " " : "\n");
|
|
toastTitle.append(title);
|
|
}
|
|
return toastTitle.toString();
|
|
}
|
|
|
|
|
|
public void checkForFontAndColors() {
|
|
try {
|
|
File colorsFile = TermuxConstants.TERMUX_COLOR_PROPERTIES_FILE;
|
|
File fontFile = TermuxConstants.TERMUX_FONT_FILE;
|
|
|
|
final Properties props = new Properties();
|
|
if (colorsFile.isFile()) {
|
|
try (InputStream in = new FileInputStream(colorsFile)) {
|
|
props.load(in);
|
|
}
|
|
}
|
|
|
|
TerminalColors.COLOR_SCHEME.updateWith(props);
|
|
TerminalSession session = mActivity.getCurrentSession();
|
|
if (session != null && session.getEmulator() != null) {
|
|
session.getEmulator().mColors.reset();
|
|
}
|
|
updateBackgroundColor();
|
|
|
|
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
|
|
mActivity.getTerminalView().setTypeface(newTypeface);
|
|
} catch (Exception e) {
|
|
Logger.logStackTraceWithMessage(LOG_TAG, "Error in checkForFontAndColors()", e);
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
}
|