@@ -89,68 +89,71 @@ import java.util.regex.Pattern;
*/
public final class TermuxActivity extends Activity implements ServiceConnection {
- private static final int CONTEXTMENU_SELECT_URL_ID = 0;
- private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
- private static final int CONTEXTMENU_PASTE_ID = 3;
- private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
- private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
- private static final int CONTEXTMENU_STYLING_ID = 6;
- private static final int CONTEXTMENU_TOGGLE_FULLSCREEN_ID = 7;
- private static final int CONTEXTMENU_HELP_ID = 8;
+ private static final int CONTEXTMENU_SELECT_URL_ID = 0;
+ private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
+ private static final int CONTEXTMENU_PASTE_ID = 3;
+ private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
+ private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
+ private static final int CONTEXTMENU_STYLING_ID = 6;
+ private static final int CONTEXTMENU_TOGGLE_FULLSCREEN_ID = 7;
+ private static final int CONTEXTMENU_HELP_ID = 8;
- private static final int MAX_SESSIONS = 8;
+ private static final int MAX_SESSIONS = 8;
- private static final int REQUESTCODE_PERMISSION_STORAGE = 1234;
+ private static final int REQUESTCODE_PERMISSION_STORAGE = 1234;
- private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
+ private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
- /** The main view of the activity showing the terminal. Initialized in onCreate(). */
- @SuppressWarnings("NullableProblems") @NonNull TerminalView mTerminalView;
+ /** The main view of the activity showing the terminal. Initialized in onCreate(). */
+ @SuppressWarnings("NullableProblems")
+ @NonNull
+ TerminalView mTerminalView;
ExtraKeysView mExtraKeysView;
- final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
+ final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
- TermuxPreferences mSettings;
+ TermuxPreferences mSettings;
- /**
- * The connection to the {@link TermuxService}. Requested in {@link #onCreate(Bundle)} with a call to
- * {@link #bindService(Intent, ServiceConnection, int)}, and obtained and stored in
- * {@link #onServiceConnected(ComponentName, IBinder)}.
- */
- TermuxService mTermService;
+ /**
+ * The connection to the {@link TermuxService}. Requested in {@link #onCreate(Bundle)} with a call to
+ * {@link #bindService(Intent, ServiceConnection, int)}, and obtained and stored in
+ * {@link #onServiceConnected(ComponentName, IBinder)}.
+ */
+ TermuxService mTermService;
- /** Initialized in {@link #onServiceConnected(ComponentName, IBinder)}. */
- ArrayAdapter mListViewAdapter;
+ /** Initialized in {@link #onServiceConnected(ComponentName, IBinder)}. */
+ ArrayAdapter mListViewAdapter;
- /** The last toast shown, used cancel current toast before showing new in {@link #showToast(String, boolean)}. */
- Toast mLastToast;
+ /** The last toast shown, used cancel current toast before showing new in {@link #showToast(String, boolean)}. */
+ Toast mLastToast;
- /**
- * If between onResume() and onStop(). Note that only one session is in the foreground of the terminal view at the
- * time, so if the session causing a change is not in the foreground it should probably be treated as background.
- */
- boolean mIsVisible;
+ /**
+ * If between onResume() and onStop(). Note that only one session is in the foreground of the terminal view at the
+ * time, so if the session causing a change is not in the foreground it should probably be treated as background.
+ */
+ boolean mIsVisible;
- 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 int mBellSoundId;
+ 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 int mBellSoundId;
- private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mIsVisible) {
- String whatToReload = intent.getStringExtra(RELOAD_STYLE_ACTION);
- if ("storage".equals(whatToReload)) {
- if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
- return;
- }
- checkForFontAndColors();
- mSettings.reloadFromProperties(TermuxActivity.this);
- }
- }
- };
+ private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mIsVisible) {
+ String whatToReload = intent.getStringExtra(RELOAD_STYLE_ACTION);
+ if ("storage".equals(whatToReload)) {
+ if (ensureStoragePermissionGranted())
+ TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
+ return;
+ }
+ checkForFontAndColors();
+ mSettings.reloadFromProperties(TermuxActivity.this);
+ }
+ }
+ };
void checkForFontAndColors() {
try {
@@ -186,35 +189,35 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
}
- /** For processes to access shared internal storage (/sdcard) we need this permission. */
- @TargetApi(Build.VERSION_CODES.M)
- public boolean ensureStoragePermissionGranted() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- return true;
- } else {
- requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
- return false;
- }
- } else {
- // Always granted before Android 6.0.
- return true;
- }
- }
+ /** For processes to access shared internal storage (/sdcard) we need this permission. */
+ @TargetApi(Build.VERSION_CODES.M)
+ public boolean ensureStoragePermissionGranted() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ } else {
+ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
+ return false;
+ }
+ } else {
+ // Always granted before Android 6.0.
+ return true;
+ }
+ }
- @Override
- public void onCreate(Bundle bundle) {
- super.onCreate(bundle);
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
mSettings = new TermuxPreferences(this);
setContentView(R.layout.drawer_layout);
- mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
+ mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
mTerminalView.setOnKeyListener(new TermuxKeyListener(this));
mTerminalView.setTextSize(mSettings.getFontSize());
- mFullScreenHelper.setImmersive(mSettings.isFullScreen());
- mTerminalView.requestFocus();
+ mFullScreenHelper.setImmersive(mSettings.isFullScreen());
+ mTerminalView.requestFocus();
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
@@ -271,41 +274,41 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
});
- View newSessionButton = findViewById(R.id.new_session_button);
- newSessionButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- addNewSession(false, null);
- }
- });
+ View newSessionButton = findViewById(R.id.new_session_button);
+ newSessionButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ addNewSession(false, null);
+ }
+ });
newSessionButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
- new DialogUtils.TextSetListener() {
- @Override
- public void onTextSet(String text) {
- addNewSession(false, text);
- }
- }, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
- @Override
- public void onTextSet(String text) {
- addNewSession(true, text);
- }
+ new DialogUtils.TextSetListener() {
+ @Override
+ public void onTextSet(String text) {
+ addNewSession(false, text);
}
- , -1, null, null);
+ }, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
+ @Override
+ public void onTextSet(String text) {
+ addNewSession(true, text);
+ }
+ }
+ , -1, null, null);
return true;
}
});
findViewById(R.id.toggle_keyboard_button).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
- getDrawer().closeDrawers();
- }
- });
+ @Override
+ public void onClick(View v) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
+ getDrawer().closeDrawers();
+ }
+ });
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() {
@Override
@@ -317,15 +320,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
registerForContextMenu(mTerminalView);
- Intent serviceIntent = new Intent(this, TermuxService.class);
- // Start the service and make it run regardless of who is bound to it:
- startService(serviceIntent);
- if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed");
+ Intent serviceIntent = new Intent(this, TermuxService.class);
+ // Start the service and make it run regardless of who is bound to it:
+ startService(serviceIntent);
+ if (!bindService(serviceIntent, this, 0))
+ throw new RuntimeException("bindService() failed");
- checkForFontAndColors();
+ checkForFontAndColors();
- mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
- }
+ mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
+ }
void toggleShowExtraKeys() {
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
@@ -337,75 +341,76 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
}
- /**
- * Part of the {@link ServiceConnection} interface. The service is bound with
- * {@link #bindService(Intent, ServiceConnection, int)} in {@link #onCreate(Bundle)} which will cause a call to this
- * callback method.
- */
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder service) {
- mTermService = ((TermuxService.LocalBinder) service).service;
+ /**
+ * Part of the {@link ServiceConnection} interface. The service is bound with
+ * {@link #bindService(Intent, ServiceConnection, int)} in {@link #onCreate(Bundle)} which will cause a call to this
+ * callback method.
+ */
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ mTermService = ((TermuxService.LocalBinder) service).service;
- mTermService.mSessionChangeCallback = new SessionChangedCallback() {
- @Override
- public void onTextChanged(TerminalSession changedSession) {
- if (!mIsVisible) return;
- if (getCurrentTermSession() == changedSession) mTerminalView.onScreenUpdated();
- }
+ mTermService.mSessionChangeCallback = new SessionChangedCallback() {
+ @Override
+ public void onTextChanged(TerminalSession changedSession) {
+ if (!mIsVisible) return;
+ if (getCurrentTermSession() == changedSession) mTerminalView.onScreenUpdated();
+ }
- @Override
- public void onTitleChanged(TerminalSession updatedSession) {
- if (!mIsVisible) return;
- if (updatedSession != getCurrentTermSession()) {
- // 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.
- showToast(toToastTitle(updatedSession), false);
- }
- mListViewAdapter.notifyDataSetChanged();
- }
+ @Override
+ public void onTitleChanged(TerminalSession updatedSession) {
+ if (!mIsVisible) return;
+ if (updatedSession != getCurrentTermSession()) {
+ // 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.
+ showToast(toToastTitle(updatedSession), false);
+ }
+ mListViewAdapter.notifyDataSetChanged();
+ }
- @Override
- public void onSessionFinished(final TerminalSession finishedSession) {
- if (mTermService.mWantsToStop) {
- // The service wants to stop as soon as possible.
- finish();
- return;
- }
+ @Override
+ public void onSessionFinished(final TerminalSession finishedSession) {
+ if (mTermService.mWantsToStop) {
+ // The service wants to stop as soon as possible.
+ finish();
+ return;
+ }
if (mIsVisible && finishedSession != getCurrentTermSession()) {
- // Show toast for non-current sessions that exit.
- int indexOfSession = mTermService.getSessions().indexOf(finishedSession);
- // Verify that session was not removed before we got told about it finishing:
- if (indexOfSession >= 0) showToast(toToastTitle(finishedSession) + " - exited", true);
- }
- mListViewAdapter.notifyDataSetChanged();
- }
+ // Show toast for non-current sessions that exit.
+ int indexOfSession = mTermService.getSessions().indexOf(finishedSession);
+ // Verify that session was not removed before we got told about it finishing:
+ if (indexOfSession >= 0)
+ showToast(toToastTitle(finishedSession) + " - exited", true);
+ }
+ mListViewAdapter.notifyDataSetChanged();
+ }
- @Override
- public void onClipboardText(TerminalSession session, String text) {
- if (!mIsVisible) return;
- showToast("Clipboard:\n\"" + text + "\"", false);
- ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(new ClipData(null, new String[] { "text/plain" }, new ClipData.Item(text)));
- }
+ @Override
+ public void onClipboardText(TerminalSession session, String text) {
+ if (!mIsVisible) return;
+ showToast("Clipboard:\n\"" + text + "\"", false);
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
+ }
- @Override
- public void onBell(TerminalSession session) {
- if (mIsVisible) {
- switch (mSettings.mBellBehaviour) {
- case TermuxPreferences.BELL_BEEP:
- mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
- break;
- case TermuxPreferences.BELL_VIBRATE:
- ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
- break;
+ @Override
+ public void onBell(TerminalSession session) {
+ if (mIsVisible) {
+ switch (mSettings.mBellBehaviour) {
+ case TermuxPreferences.BELL_BEEP:
+ mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
+ break;
+ case TermuxPreferences.BELL_VIBRATE:
+ ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
+ break;
case TermuxPreferences.BELL_IGNORE:
// Ignore the bell character.
break;
- }
+ }
- }
- }
+ }
+ }
@Override
public void onColorsChanged(TerminalSession changedSession) {
@@ -413,98 +418,98 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
};
- ListView listView = (ListView) findViewById(R.id.left_drawer_list);
- mListViewAdapter = new ArrayAdapter(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
- final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
- final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
+ ListView listView = (ListView) findViewById(R.id.left_drawer_list);
+ mListViewAdapter = new ArrayAdapter(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
+ final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
+ final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View row = convertView;
- if (row == null) {
- LayoutInflater inflater = getLayoutInflater();
- row = inflater.inflate(R.layout.line_in_drawer, parent, false);
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = convertView;
+ if (row == null) {
+ LayoutInflater inflater = getLayoutInflater();
+ row = inflater.inflate(R.layout.line_in_drawer, parent, false);
+ }
- TerminalSession sessionAtRow = getItem(position);
- boolean sessionRunning = sessionAtRow.isRunning();
+ TerminalSession sessionAtRow = getItem(position);
+ boolean sessionRunning = sessionAtRow.isRunning();
- TextView firstLineView = (TextView) row.findViewById(R.id.row_line);
+ TextView firstLineView = (TextView) row.findViewById(R.id.row_line);
- String name = sessionAtRow.mSessionName;
- String sessionTitle = sessionAtRow.getTitle();
+ String name = sessionAtRow.mSessionName;
+ String sessionTitle = sessionAtRow.getTitle();
- String numberPart = "[" + (position + 1) + "] ";
- String sessionNamePart = (TextUtils.isEmpty(name) ? "" : name);
- String sessionTitlePart = (TextUtils.isEmpty(sessionTitle) ? "" : ((sessionNamePart.isEmpty() ? "" : "\n") + sessionTitle));
+ String numberPart = "[" + (position + 1) + "] ";
+ String sessionNamePart = (TextUtils.isEmpty(name) ? "" : name);
+ String sessionTitlePart = (TextUtils.isEmpty(sessionTitle) ? "" : ((sessionNamePart.isEmpty() ? "" : "\n") + sessionTitle));
- String text = numberPart + sessionNamePart + sessionTitlePart;
- SpannableString styledText = new SpannableString(text);
- styledText.setSpan(boldSpan, 0, numberPart.length() + sessionNamePart.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- styledText.setSpan(italicSpan, numberPart.length() + sessionNamePart.length(), text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ String text = numberPart + sessionNamePart + sessionTitlePart;
+ SpannableString styledText = new SpannableString(text);
+ styledText.setSpan(boldSpan, 0, numberPart.length() + sessionNamePart.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ styledText.setSpan(italicSpan, numberPart.length() + sessionNamePart.length(), text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- firstLineView.setText(styledText);
+ firstLineView.setText(styledText);
- if (sessionRunning) {
- firstLineView.setPaintFlags(firstLineView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
- } else {
- firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
- }
- int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? Color.BLACK : Color.RED;
- firstLineView.setTextColor(color);
- return row;
- }
- };
- listView.setAdapter(mListViewAdapter);
- listView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- TerminalSession clickedSession = mListViewAdapter.getItem(position);
- switchToSession(clickedSession);
- getDrawer().closeDrawers();
- }
- });
- listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView> parent, View view, final int position, long id) {
- final TerminalSession selectedSession = mListViewAdapter.getItem(position);
- renameSession(selectedSession);
- return true;
- }
- });
+ if (sessionRunning) {
+ firstLineView.setPaintFlags(firstLineView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
+ } else {
+ firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ }
+ int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? Color.BLACK : Color.RED;
+ firstLineView.setTextColor(color);
+ return row;
+ }
+ };
+ listView.setAdapter(mListViewAdapter);
+ listView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ TerminalSession clickedSession = mListViewAdapter.getItem(position);
+ switchToSession(clickedSession);
+ getDrawer().closeDrawers();
+ }
+ });
+ listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, final int position, long id) {
+ final TerminalSession selectedSession = mListViewAdapter.getItem(position);
+ renameSession(selectedSession);
+ return true;
+ }
+ });
- if (mTermService.getSessions().isEmpty()) {
- if (mIsVisible) {
- TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
- @Override
- public void run() {
- if (mTermService == null) return; // Activity might have been destroyed.
- try {
- if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
- new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
- .setCancelable(false).setPositiveButton(android.R.string.ok, null)
- .setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
- dialog.dismiss();
- }
- }).show();
- }
- addNewSession(false, null);
- } catch (WindowManager.BadTokenException e) {
- // Activity finished - ignore.
- }
- }
- });
- } else {
- // The service connected while not in foreground - just bail out.
- finish();
- }
- } else {
- switchToSession(getStoredCurrentSessionOrLast());
- }
- }
+ if (mTermService.getSessions().isEmpty()) {
+ if (mIsVisible) {
+ TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
+ @Override
+ public void run() {
+ if (mTermService == null) return; // Activity might have been destroyed.
+ try {
+ if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
+ new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
+ .setCancelable(false).setPositiveButton(android.R.string.ok, null)
+ .setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
+ dialog.dismiss();
+ }
+ }).show();
+ }
+ addNewSession(false, null);
+ } catch (WindowManager.BadTokenException e) {
+ // Activity finished - ignore.
+ }
+ }
+ });
+ } else {
+ // The service connected while not in foreground - just bail out.
+ finish();
+ }
+ } else {
+ switchToSession(getStoredCurrentSessionOrLast());
+ }
+ }
public void switchToSession(boolean forward) {
TerminalSession currentSession = getCurrentTermSession();
@@ -517,8 +522,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
switchToSession(mTermService.getSessions().get(index));
}
- @SuppressLint("InflateParams")
- void renameSession(final TerminalSession sessionToRename) {
+ @SuppressLint("InflateParams")
+ void renameSession(final TerminalSession sessionToRename) {
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
@@ -527,189 +532,190 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}, -1, null, -1, null, null);
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (mTermService != null) {
- // Respect being stopped from the TermuxService notification action.
- finish();
- }
- }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (mTermService != null) {
+ // Respect being stopped from the TermuxService notification action.
+ finish();
+ }
+ }
- @Nullable TerminalSession getCurrentTermSession() {
- return mTerminalView.getCurrentSession();
- }
+ @Nullable
+ TerminalSession getCurrentTermSession() {
+ return mTerminalView.getCurrentSession();
+ }
- @Override
- public void onStart() {
- super.onStart();
- mIsVisible = true;
+ @Override
+ public void onStart() {
+ super.onStart();
+ mIsVisible = true;
- if (mTermService != null) {
- // The service has connected, but data may have changed since we were last in the foreground.
- switchToSession(getStoredCurrentSessionOrLast());
- mListViewAdapter.notifyDataSetChanged();
- }
+ if (mTermService != null) {
+ // The service has connected, but data may have changed since we were last in the foreground.
+ switchToSession(getStoredCurrentSessionOrLast());
+ mListViewAdapter.notifyDataSetChanged();
+ }
- registerReceiver(mBroadcastReceiever, new IntentFilter(RELOAD_STYLE_ACTION));
+ registerReceiver(mBroadcastReceiever, new IntentFilter(RELOAD_STYLE_ACTION));
- // The current terminal session may have changed while being away, force
- // a refresh of the displayed terminal:
- mTerminalView.onScreenUpdated();
- }
+ // The current terminal session may have changed while being away, force
+ // a refresh of the displayed terminal:
+ mTerminalView.onScreenUpdated();
+ }
- @Override
- protected void onStop() {
- super.onStop();
- mIsVisible = false;
- TerminalSession currentSession = getCurrentTermSession();
- if (currentSession != null) TermuxPreferences.storeCurrentSession(this, currentSession);
- unregisterReceiver(mBroadcastReceiever);
- getDrawer().closeDrawers();
- }
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mIsVisible = false;
+ TerminalSession currentSession = getCurrentTermSession();
+ if (currentSession != null) TermuxPreferences.storeCurrentSession(this, currentSession);
+ unregisterReceiver(mBroadcastReceiever);
+ getDrawer().closeDrawers();
+ }
- @Override
- public void onBackPressed() {
- if (getDrawer().isDrawerOpen(Gravity.LEFT)) {
+ @Override
+ public void onBackPressed() {
+ if (getDrawer().isDrawerOpen(Gravity.LEFT)) {
getDrawer().closeDrawers();
} else {
finish();
}
- }
+ }
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mTermService != null) {
- // Do not leave service with references to activity.
- mTermService.mSessionChangeCallback = null;
- mTermService = null;
- }
- unbindService(this);
- }
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mTermService != null) {
+ // Do not leave service with references to activity.
+ mTermService.mSessionChangeCallback = null;
+ mTermService = null;
+ }
+ unbindService(this);
+ }
- DrawerLayout getDrawer() {
- return (DrawerLayout) findViewById(R.id.drawer_layout);
- }
+ DrawerLayout getDrawer() {
+ return (DrawerLayout) findViewById(R.id.drawer_layout);
+ }
- void addNewSession(boolean failSafe, String sessionName) {
- if (mTermService.getSessions().size() >= MAX_SESSIONS) {
- new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
- .setPositiveButton(android.R.string.ok, null).show();
- } else {
- String executablePath = (failSafe ? "/system/bin/sh" : null);
- TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe);
- if (sessionName != null) {
- newSession.mSessionName = sessionName;
- }
- switchToSession(newSession);
- getDrawer().closeDrawers();
- }
- }
+ void addNewSession(boolean failSafe, String sessionName) {
+ if (mTermService.getSessions().size() >= MAX_SESSIONS) {
+ new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
+ .setPositiveButton(android.R.string.ok, null).show();
+ } else {
+ String executablePath = (failSafe ? "/system/bin/sh" : null);
+ TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe);
+ if (sessionName != null) {
+ newSession.mSessionName = sessionName;
+ }
+ switchToSession(newSession);
+ getDrawer().closeDrawers();
+ }
+ }
- /** Try switching to session and note about it, but do nothing if already displaying the session. */
- void switchToSession(TerminalSession session) {
- if (mTerminalView.attachSession(session)) {
+ /** Try switching to session and note about it, but do nothing if already displaying the session. */
+ void switchToSession(TerminalSession session) {
+ if (mTerminalView.attachSession(session)) {
noteSessionInfo();
updateBackgroundColor();
}
- }
+ }
- String toToastTitle(TerminalSession session) {
- final int indexOfSession = mTermService.getSessions().indexOf(session);
- 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();
- }
+ String toToastTitle(TerminalSession session) {
+ final int indexOfSession = mTermService.getSessions().indexOf(session);
+ 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();
+ }
- void noteSessionInfo() {
- if (!mIsVisible) return;
- TerminalSession session = getCurrentTermSession();
- final int indexOfSession = mTermService.getSessions().indexOf(session);
- showToast(toToastTitle(session), false);
- mListViewAdapter.notifyDataSetChanged();
- final ListView lv = ((ListView) findViewById(R.id.left_drawer_list));
- lv.setItemChecked(indexOfSession, true);
- lv.smoothScrollToPosition(indexOfSession);
- }
+ void noteSessionInfo() {
+ if (!mIsVisible) return;
+ TerminalSession session = getCurrentTermSession();
+ final int indexOfSession = mTermService.getSessions().indexOf(session);
+ showToast(toToastTitle(session), false);
+ mListViewAdapter.notifyDataSetChanged();
+ final ListView lv = ((ListView) findViewById(R.id.left_drawer_list));
+ lv.setItemChecked(indexOfSession, true);
+ lv.smoothScrollToPosition(indexOfSession);
+ }
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- TerminalSession currentSession = getCurrentTermSession();
- if (currentSession == null) return;
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ TerminalSession currentSession = getCurrentTermSession();
+ if (currentSession == null) return;
- menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url);
- menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
- menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
- menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
- menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
- menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
- menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
- }
+ menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url);
+ menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
+ menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
+ menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
+ menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
+ menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
+ menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
+ }
- /** Hook system menu to show context menu instead. */
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- mTerminalView.showContextMenu();
- return false;
- }
+ /** Hook system menu to show context menu instead. */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ mTerminalView.showContextMenu();
+ return false;
+ }
- static LinkedHashSet extractUrls(String text) {
- // Pattern for recognizing a URL, based off RFC 3986
- // http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
- final Pattern urlPattern = Pattern.compile(
- "(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?\\-+()\\[\\]\\*$~@!:/{};']*)",
- Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
- LinkedHashSet urlSet = new LinkedHashSet<>();
- Matcher matcher = urlPattern.matcher(text);
- while (matcher.find()) {
- int matchStart = matcher.start(1);
- int matchEnd = matcher.end();
- String url = text.substring(matchStart, matchEnd);
- urlSet.add(url);
- }
- return urlSet;
- }
+ static LinkedHashSet extractUrls(String text) {
+ // Pattern for recognizing a URL, based off RFC 3986
+ // http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
+ final Pattern urlPattern = Pattern.compile(
+ "(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?\\-+()\\[\\]\\*$~@!:/{};']*)",
+ Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
+ LinkedHashSet urlSet = new LinkedHashSet<>();
+ Matcher matcher = urlPattern.matcher(text);
+ while (matcher.find()) {
+ int matchStart = matcher.start(1);
+ int matchEnd = matcher.end();
+ String url = text.substring(matchStart, matchEnd);
+ urlSet.add(url);
+ }
+ return urlSet;
+ }
- void showUrlSelection() {
- String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
- LinkedHashSet urlSet = extractUrls(text);
- if (urlSet.isEmpty()) {
- new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
- return;
- }
+ void showUrlSelection() {
+ String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
+ LinkedHashSet urlSet = extractUrls(text);
+ if (urlSet.isEmpty()) {
+ new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
+ return;
+ }
- final CharSequence[] urls = urlSet.toArray(new CharSequence[urlSet.size()]);
- Collections.reverse(Arrays.asList(urls)); // Latest first.
+ final CharSequence[] urls = urlSet.toArray(new CharSequence[urlSet.size()]);
+ Collections.reverse(Arrays.asList(urls)); // Latest first.
- // Click to copy url to clipboard:
- final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface di, int which) {
- String url = (String) urls[which];
- ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(new ClipData(null, new String[] { "text/plain" }, new ClipData.Item(url)));
- Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
- }
- }).setTitle(R.string.select_url_dialog_title).create();
+ // Click to copy url to clipboard:
+ final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface di, int which) {
+ String url = (String) urls[which];
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(url)));
+ Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
+ }
+ }).setTitle(R.string.select_url_dialog_title).create();
- // Long press to open URL:
- dialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface di) {
- ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
- lv.setOnItemLongClickListener(new OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
- dialog.dismiss();
- String url = (String) urls[position];
+ // Long press to open URL:
+ dialog.setOnShowListener(new OnShowListener() {
+ @Override
+ public void onShow(DialogInterface di) {
+ ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
+ lv.setOnItemLongClickListener(new OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
+ dialog.dismiss();
+ String url = (String) urls[position];
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
startActivity(i, null);
@@ -717,56 +723,56 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// If no applications match, Android displays a system message.
startActivity(Intent.createChooser(i, null));
}
- return true;
- }
- });
- }
- });
+ return true;
+ }
+ });
+ }
+ });
- dialog.show();
- }
+ dialog.show();
+ }
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- TerminalSession session = getCurrentTermSession();
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ TerminalSession session = getCurrentTermSession();
- switch (item.getItemId()) {
- case CONTEXTMENU_SELECT_URL_ID:
- showUrlSelection();
- return true;
- case CONTEXTMENU_SHARE_TRANSCRIPT_ID:
- if (session != null) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim());
- intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
- startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
- }
- return true;
- case CONTEXTMENU_PASTE_ID:
- doPaste();
- return true;
- case CONTEXTMENU_KILL_PROCESS_ID:
- final AlertDialog.Builder b = new AlertDialog.Builder(this);
- b.setIcon(android.R.drawable.ic_dialog_alert);
- b.setMessage(R.string.confirm_kill_process);
- b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.dismiss();
- getCurrentTermSession().finishIfRunning();
- }
- });
- b.setNegativeButton(android.R.string.no, null);
- b.show();
- return true;
- case CONTEXTMENU_RESET_TERMINAL_ID: {
- if (session != null) {
- session.reset();
- showToast(getResources().getString(R.string.reset_toast_notification), true);
- }
- return true;
- }
+ switch (item.getItemId()) {
+ case CONTEXTMENU_SELECT_URL_ID:
+ showUrlSelection();
+ return true;
+ case CONTEXTMENU_SHARE_TRANSCRIPT_ID:
+ if (session != null) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim());
+ intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
+ startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
+ }
+ return true;
+ case CONTEXTMENU_PASTE_ID:
+ doPaste();
+ return true;
+ case CONTEXTMENU_KILL_PROCESS_ID:
+ final AlertDialog.Builder b = new AlertDialog.Builder(this);
+ b.setIcon(android.R.drawable.ic_dialog_alert);
+ b.setMessage(R.string.confirm_kill_process);
+ b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ getCurrentTermSession().finishIfRunning();
+ }
+ });
+ b.setNegativeButton(android.R.string.no, null);
+ b.show();
+ return true;
+ case CONTEXTMENU_RESET_TERMINAL_ID: {
+ if (session != null) {
+ session.reset();
+ showToast(getResources().getString(R.string.reset_toast_notification), true);
+ }
+ return true;
+ }
case CONTEXTMENU_STYLING_ID: {
Intent stylingIntent = new Intent();
stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity");
@@ -784,59 +790,60 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}).setNegativeButton(android.R.string.cancel, null).show();
}
}
- return true;
- case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
- toggleImmersive();
- return true;
- case CONTEXTMENU_HELP_ID:
- startActivity(new Intent(this, TermuxHelpActivity.class));
- return true;
- default:
- return super.onContextItemSelected(item);
- }
- }
+ return true;
+ case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
+ toggleImmersive();
+ return true;
+ case CONTEXTMENU_HELP_ID:
+ startActivity(new Intent(this, TermuxHelpActivity.class));
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
- if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- TermuxInstaller.setupStorageSymlinks(this);
- }
- }
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ TermuxInstaller.setupStorageSymlinks(this);
+ }
+ }
- void toggleImmersive() {
- boolean newValue = !mSettings.isFullScreen();
- mSettings.setFullScreen(this, newValue);
- mFullScreenHelper.setImmersive(newValue);
- }
+ void toggleImmersive() {
+ boolean newValue = !mSettings.isFullScreen();
+ mSettings.setFullScreen(this, newValue);
+ mFullScreenHelper.setImmersive(newValue);
+ }
- void changeFontSize(boolean increase) {
- mSettings.changeFontSize(this, increase);
- mTerminalView.setTextSize(mSettings.getFontSize());
- }
+ void changeFontSize(boolean increase) {
+ mSettings.changeFontSize(this, increase);
+ mTerminalView.setTextSize(mSettings.getFontSize());
+ }
- void doPaste() {
- ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clipData = clipboard.getPrimaryClip();
- if (clipData == null) return;
- CharSequence paste = clipData.getItemAt(0).coerceToText(this);
- if (!TextUtils.isEmpty(paste)) getCurrentTermSession().getEmulator().paste(paste.toString());
- }
+ void doPaste() {
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clipData = clipboard.getPrimaryClip();
+ if (clipData == null) return;
+ CharSequence paste = clipData.getItemAt(0).coerceToText(this);
+ if (!TextUtils.isEmpty(paste))
+ getCurrentTermSession().getEmulator().paste(paste.toString());
+ }
- /** The current session as stored or the last one if that does not exist. */
- public TerminalSession getStoredCurrentSessionOrLast() {
- TerminalSession stored = TermuxPreferences.getCurrentSession(this);
- if (stored != null) return stored;
- int numberOfSessions = mTermService.getSessions().size();
- if (numberOfSessions == 0) return null;
- return mTermService.getSessions().get(numberOfSessions - 1);
- }
+ /** The current session as stored or the last one if that does not exist. */
+ public TerminalSession getStoredCurrentSessionOrLast() {
+ TerminalSession stored = TermuxPreferences.getCurrentSession(this);
+ if (stored != null) return stored;
+ int numberOfSessions = mTermService.getSessions().size();
+ if (numberOfSessions == 0) return null;
+ return mTermService.getSessions().get(numberOfSessions - 1);
+ }
- /** Show a toast and dismiss the last one if still visible. */
- void showToast(String text, boolean longDuration) {
- if (mLastToast != null) mLastToast.cancel();
- mLastToast = Toast.makeText(TermuxActivity.this, text, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
- mLastToast.setGravity(Gravity.TOP, 0, 0);
- mLastToast.show();
- }
+ /** Show a toast and dismiss the last one if still visible. */
+ void showToast(String text, boolean longDuration) {
+ if (mLastToast != null) mLastToast.cancel();
+ mLastToast = Toast.makeText(TermuxActivity.this, text, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
+ mLastToast.setGravity(Gravity.TOP, 0, 0);
+ mLastToast.show();
+ }
}
diff --git a/app/src/main/java/com/termux/app/TermuxHelpActivity.java b/app/src/main/java/com/termux/app/TermuxHelpActivity.java
index f9432efd..aa5a164b 100644
--- a/app/src/main/java/com/termux/app/TermuxHelpActivity.java
+++ b/app/src/main/java/com/termux/app/TermuxHelpActivity.java
@@ -15,61 +15,61 @@ import android.widget.RelativeLayout;
/** Basic embedded browser for viewing help pages. */
public final class TermuxHelpActivity extends Activity {
- private WebView mWebView;
+ private WebView mWebView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- final RelativeLayout progressLayout = new RelativeLayout(this);
- RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- lParams.addRule(RelativeLayout.CENTER_IN_PARENT);
- ProgressBar progressBar = new ProgressBar(this);
- progressBar.setIndeterminate(true);
- progressBar.setLayoutParams(lParams);
- progressLayout.addView(progressBar);
+ final RelativeLayout progressLayout = new RelativeLayout(this);
+ RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ lParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+ ProgressBar progressBar = new ProgressBar(this);
+ progressBar.setIndeterminate(true);
+ progressBar.setLayoutParams(lParams);
+ progressLayout.addView(progressBar);
- mWebView = new WebView(this);
- WebSettings settings = mWebView.getSettings();
- settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
- settings.setAppCacheEnabled(false);
- setContentView(progressLayout);
- mWebView.clearCache(true);
+ mWebView = new WebView(this);
+ WebSettings settings = mWebView.getSettings();
+ settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
+ settings.setAppCacheEnabled(false);
+ setContentView(progressLayout);
+ mWebView.clearCache(true);
- mWebView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- if (url.startsWith("https://termux.com")) {
- // Inline help.
- setContentView(progressLayout);
- return false;
- }
+ mWebView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (url.startsWith("https://termux.com")) {
+ // Inline help.
+ setContentView(progressLayout);
+ return false;
+ }
- try {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- } catch (ActivityNotFoundException e) {
- // Android TV does not have a system browser.
- setContentView(progressLayout);
- return false;
- }
- return true;
- }
+ try {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ } catch (ActivityNotFoundException e) {
+ // Android TV does not have a system browser.
+ setContentView(progressLayout);
+ return false;
+ }
+ return true;
+ }
- @Override
- public void onPageFinished(WebView view, String url) {
- setContentView(mWebView);
- }
- });
- mWebView.loadUrl("https://termux.com/help.html");
- }
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ setContentView(mWebView);
+ }
+ });
+ mWebView.loadUrl("https://termux.com/help.html");
+ }
- @Override
- public void onBackPressed() {
- if (mWebView.canGoBack()) {
- mWebView.goBack();
- } else {
- super.onBackPressed();
- }
- }
+ @Override
+ public void onBackPressed() {
+ if (mWebView.canGoBack()) {
+ mWebView.goBack();
+ } else {
+ super.onBackPressed();
+ }
+ }
}
diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java
index 5c5afd14..444e811d 100644
--- a/app/src/main/java/com/termux/app/TermuxInstaller.java
+++ b/app/src/main/java/com/termux/app/TermuxInstaller.java
@@ -31,152 +31,155 @@ import java.util.zip.ZipInputStream;
/**
* Install the Termux bootstrap packages if necessary by following the below steps:
- *
+ *
* (1) If $PREFIX already exist, assume that it is correct and be done. Note that this relies on that we do not create a
* broken $PREFIX folder below.
- *
+ *
* (2) A progress dialog is shown with "Installing..." message and a spinner.
- *
+ *
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
- *
+ *
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
- *
+ *
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
* continously encountering zip file entries:
- *
+ *
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
- *
+ *
* (5.2) For every other zip entry, extract it into $STAGING_PREFIX and set execute permissions if necessary.
*/
final class TermuxInstaller {
- /** Performs setup if necessary. */
- static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
- // Termux can only be run as the primary user (device owner) since only that
- // account has the expected file system paths. Verify that:
- android.os.UserManager um = (android.os.UserManager) activity.getSystemService(Context.USER_SERVICE);
- boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
- if (!isPrimaryUser) {
- new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
- .setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- System.exit(0);
- }
- }).setPositiveButton(android.R.string.ok, null).show();
- return;
- }
+ /** Performs setup if necessary. */
+ static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
+ // Termux can only be run as the primary user (device owner) since only that
+ // account has the expected file system paths. Verify that:
+ android.os.UserManager um = (android.os.UserManager) activity.getSystemService(Context.USER_SERVICE);
+ boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
+ if (!isPrimaryUser) {
+ new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
+ .setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ System.exit(0);
+ }
+ }).setPositiveButton(android.R.string.ok, null).show();
+ return;
+ }
- final File PREFIX_FILE = new File(TermuxService.PREFIX_PATH);
- if (PREFIX_FILE.isDirectory()) {
- whenDone.run();
- return;
- }
+ final File PREFIX_FILE = new File(TermuxService.PREFIX_PATH);
+ if (PREFIX_FILE.isDirectory()) {
+ whenDone.run();
+ return;
+ }
- final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.bootstrap_installer_body), true, false);
- new Thread() {
- @Override
- public void run() {
- try {
- final String STAGING_PREFIX_PATH = TermuxService.FILES_PATH + "/usr-staging";
- final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH);
+ final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.bootstrap_installer_body), true, false);
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ final String STAGING_PREFIX_PATH = TermuxService.FILES_PATH + "/usr-staging";
+ final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH);
- if (STAGING_PREFIX_FILE.exists()) {
- deleteFolder(STAGING_PREFIX_FILE);
- }
+ if (STAGING_PREFIX_FILE.exists()) {
+ deleteFolder(STAGING_PREFIX_FILE);
+ }
- final byte[] buffer = new byte[8096];
- final List> symlinks = new ArrayList<>(50);
+ final byte[] buffer = new byte[8096];
+ final List> symlinks = new ArrayList<>(50);
- final URL zipUrl = determineZipUrl();
- try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
- ZipEntry zipEntry;
- while ((zipEntry = zipInput.getNextEntry()) != null) {
- if (zipEntry.getName().equals("SYMLINKS.txt")) {
- BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput));
- String line;
- while ((line = symlinksReader.readLine()) != null) {
- String[] parts = line.split("←");
- if (parts.length != 2) throw new RuntimeException("Malformed symlink line: " + line);
- String oldPath = parts[0];
- String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
- symlinks.add(Pair.create(oldPath, newPath));
- }
- } else {
- String zipEntryName = zipEntry.getName();
- File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
- if (zipEntry.isDirectory()) {
- if (!targetFile.mkdirs()) throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
- } else {
- try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
- int readBytes;
- while ((readBytes = zipInput.read(buffer)) != -1)
- outStream.write(buffer, 0, readBytes);
- }
- if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) {
- //noinspection OctalInteger
- Os.chmod(targetFile.getAbsolutePath(), 0700);
- }
- }
- }
- }
- }
+ final URL zipUrl = determineZipUrl();
+ try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
+ ZipEntry zipEntry;
+ while ((zipEntry = zipInput.getNextEntry()) != null) {
+ if (zipEntry.getName().equals("SYMLINKS.txt")) {
+ BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput));
+ String line;
+ while ((line = symlinksReader.readLine()) != null) {
+ String[] parts = line.split("←");
+ if (parts.length != 2)
+ throw new RuntimeException("Malformed symlink line: " + line);
+ String oldPath = parts[0];
+ String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
+ symlinks.add(Pair.create(oldPath, newPath));
+ }
+ } else {
+ String zipEntryName = zipEntry.getName();
+ File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
+ if (zipEntry.isDirectory()) {
+ if (!targetFile.mkdirs())
+ throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
+ } else {
+ try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
+ int readBytes;
+ while ((readBytes = zipInput.read(buffer)) != -1)
+ outStream.write(buffer, 0, readBytes);
+ }
+ if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) {
+ //noinspection OctalInteger
+ Os.chmod(targetFile.getAbsolutePath(), 0700);
+ }
+ }
+ }
+ }
+ }
- if (symlinks.isEmpty()) throw new RuntimeException("No SYMLINKS.txt encountered");
- for (Pair symlink : symlinks) {
- Os.symlink(symlink.first, symlink.second);
- }
+ if (symlinks.isEmpty())
+ throw new RuntimeException("No SYMLINKS.txt encountered");
+ for (Pair symlink : symlinks) {
+ Os.symlink(symlink.first, symlink.second);
+ }
- if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) {
- throw new RuntimeException("Unable to rename staging folder");
- }
+ if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) {
+ throw new RuntimeException("Unable to rename staging folder");
+ }
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- whenDone.run();
- }
- });
- } catch (final Exception e) {
- Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
- .setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- activity.finish();
- }
- }).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- TermuxInstaller.setupIfNeeded(activity, whenDone);
- }
- }).show();
- } catch (WindowManager.BadTokenException e) {
- // Activity already dismissed - ignore.
- }
- }
- });
- } finally {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- progress.dismiss();
- } catch (RuntimeException e) {
- // Activity already dismissed - ignore.
- }
- }
- });
- }
- }
- }.start();
- }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ whenDone.run();
+ }
+ });
+ } catch (final Exception e) {
+ Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
+ .setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ activity.finish();
+ }
+ }).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ TermuxInstaller.setupIfNeeded(activity, whenDone);
+ }
+ }).show();
+ } catch (WindowManager.BadTokenException e) {
+ // Activity already dismissed - ignore.
+ }
+ }
+ });
+ } finally {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ progress.dismiss();
+ } catch (RuntimeException e) {
+ // Activity already dismissed - ignore.
+ }
+ }
+ });
+ }
+ }
+ }.start();
+ }
/** Get bootstrap zip url for this systems cpu architecture. */
static URL determineZipUrl() throws MalformedURLException {
@@ -201,64 +204,64 @@ final class TermuxInstaller {
return new URL("https://termux.net/bootstrap/bootstrap-" + termuxArch + ".zip");
}
- /** Delete a folder and all its content or throw. */
- static void deleteFolder(File fileOrDirectory) {
- File[] children = fileOrDirectory.listFiles();
- if (children != null) {
- for (File child : children) {
- deleteFolder(child);
- }
- }
- if (!fileOrDirectory.delete()) {
- throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
- }
- }
+ /** Delete a folder and all its content or throw. */
+ static void deleteFolder(File fileOrDirectory) {
+ File[] children = fileOrDirectory.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ deleteFolder(child);
+ }
+ }
+ if (!fileOrDirectory.delete()) {
+ throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
+ }
+ }
- public static void setupStorageSymlinks(final Context context) {
- final String LOG_TAG = "termux-storage";
- new Thread() {
- public void run() {
- try {
- File storageDir = new File(TermuxService.HOME_PATH, "storage");
+ public static void setupStorageSymlinks(final Context context) {
+ final String LOG_TAG = "termux-storage";
+ new Thread() {
+ public void run() {
+ try {
+ File storageDir = new File(TermuxService.HOME_PATH, "storage");
- if (storageDir.exists() && !storageDir.delete()) {
- Log.e(LOG_TAG, "Could not delete old $HOME/storage");
- return;
- }
+ if (storageDir.exists() && !storageDir.delete()) {
+ Log.e(LOG_TAG, "Could not delete old $HOME/storage");
+ return;
+ }
- if (!storageDir.mkdirs()) {
- Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage");
- return;
- }
+ if (!storageDir.mkdirs()) {
+ Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage");
+ return;
+ }
- File sharedDir = Environment.getExternalStorageDirectory();
- Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath());
+ File sharedDir = Environment.getExternalStorageDirectory();
+ Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath());
- File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- Os.symlink(downloadsDir.getAbsolutePath(), new File(storageDir, "downloads").getAbsolutePath());
+ File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ Os.symlink(downloadsDir.getAbsolutePath(), new File(storageDir, "downloads").getAbsolutePath());
- File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
- Os.symlink(dcimDir.getAbsolutePath(), new File(storageDir, "dcim").getAbsolutePath());
+ File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
+ Os.symlink(dcimDir.getAbsolutePath(), new File(storageDir, "dcim").getAbsolutePath());
- File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
- Os.symlink(picturesDir.getAbsolutePath(), new File(storageDir, "pictures").getAbsolutePath());
+ File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+ Os.symlink(picturesDir.getAbsolutePath(), new File(storageDir, "pictures").getAbsolutePath());
- File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
- Os.symlink(musicDir.getAbsolutePath(), new File(storageDir, "music").getAbsolutePath());
+ File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
+ Os.symlink(musicDir.getAbsolutePath(), new File(storageDir, "music").getAbsolutePath());
- File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
- Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
+ File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
+ Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
- final File[] dirs = context.getExternalFilesDirs(null);
- if (dirs != null && dirs.length >= 2) {
- final File externalDir = dirs[1];
- Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "Error setting up link", e);
- }
- }
- }.start();
- }
+ final File[] dirs = context.getExternalFilesDirs(null);
+ if (dirs != null && dirs.length >= 2) {
+ final File externalDir = dirs[1];
+ Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error setting up link", e);
+ }
+ }
+ }.start();
+ }
}
diff --git a/app/src/main/java/com/termux/app/TermuxKeyListener.java b/app/src/main/java/com/termux/app/TermuxKeyListener.java
index df9d1207..7c609b4d 100644
--- a/app/src/main/java/com/termux/app/TermuxKeyListener.java
+++ b/app/src/main/java/com/termux/app/TermuxKeyListener.java
@@ -111,7 +111,8 @@ public final class TermuxKeyListener implements TerminalKeyListener {
mActivity.changeFontSize(false);
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
int num = unicodeChar - '1';
- if (service.getSessions().size() > num) mActivity.switchToSession(service.getSessions().get(num));
+ if (service.getSessions().size() > num)
+ mActivity.switchToSession(service.getSessions().get(num));
}
return true;
}
@@ -235,7 +236,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
session.writeCodePoint(altDown, resultingCodePoint);
}
return true;
- } else if (ctrlDown) {
+ } else if (ctrlDown) {
List shortcuts = mActivity.mSettings.shortcuts;
if (!shortcuts.isEmpty()) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
diff --git a/app/src/main/java/com/termux/app/TermuxPreferences.java b/app/src/main/java/com/termux/app/TermuxPreferences.java
index a4ef7798..58be51a4 100644
--- a/app/src/main/java/com/termux/app/TermuxPreferences.java
+++ b/app/src/main/java/com/termux/app/TermuxPreferences.java
@@ -20,66 +20,67 @@ import java.util.Properties;
final class TermuxPreferences {
- @IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
- @Retention(RetentionPolicy.SOURCE)
- public @interface AsciiBellBehaviour {}
+ @IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AsciiBellBehaviour {
+ }
- static final int BELL_VIBRATE = 1;
- static final int BELL_BEEP = 2;
- static final int BELL_IGNORE = 3;
+ static final int BELL_VIBRATE = 1;
+ static final int BELL_BEEP = 2;
+ static final int BELL_IGNORE = 3;
- private final int MIN_FONTSIZE;
- private static final int MAX_FONTSIZE = 256;
+ private final int MIN_FONTSIZE;
+ private static final int MAX_FONTSIZE = 256;
- private static final String FULLSCREEN_KEY = "fullscreen";
+ private static final String FULLSCREEN_KEY = "fullscreen";
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
- private static final String FONTSIZE_KEY = "fontsize";
- private static final String CURRENT_SESSION_KEY = "current_session";
- private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
+ private static final String FONTSIZE_KEY = "fontsize";
+ private static final String CURRENT_SESSION_KEY = "current_session";
+ private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
- private boolean mFullScreen;
- private int mFontSize;
+ private boolean mFullScreen;
+ private int mFontSize;
- @AsciiBellBehaviour
- int mBellBehaviour = BELL_VIBRATE;
+ @AsciiBellBehaviour
+ int mBellBehaviour = BELL_VIBRATE;
boolean mBackIsEscape;
boolean mShowExtraKeys;
- TermuxPreferences(Context context) {
- reloadFromProperties(context);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ TermuxPreferences(Context context) {
+ reloadFromProperties(context);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
+ float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
- // This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size
- // to prevent invisible text due to zoom be mistake:
- MIN_FONTSIZE = (int) (4f * dipInPixels);
+ // This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size
+ // to prevent invisible text due to zoom be mistake:
+ MIN_FONTSIZE = (int) (4f * dipInPixels);
- mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
+ mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
- // http://www.google.com/design/spec/style/typography.html#typography-line-height
- int defaultFontSize = Math.round(12 * dipInPixels);
- // Make it divisible by 2 since that is the minimal adjustment step:
- if (defaultFontSize % 2 == 1) defaultFontSize--;
+ // http://www.google.com/design/spec/style/typography.html#typography-line-height
+ int defaultFontSize = Math.round(12 * dipInPixels);
+ // Make it divisible by 2 since that is the minimal adjustment step:
+ if (defaultFontSize % 2 == 1) defaultFontSize--;
- try {
- mFontSize = Integer.parseInt(prefs.getString(FONTSIZE_KEY, Integer.toString(defaultFontSize)));
- } catch (NumberFormatException | ClassCastException e) {
- mFontSize = defaultFontSize;
- }
- mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
- }
+ try {
+ mFontSize = Integer.parseInt(prefs.getString(FONTSIZE_KEY, Integer.toString(defaultFontSize)));
+ } catch (NumberFormatException | ClassCastException e) {
+ mFontSize = defaultFontSize;
+ }
+ mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
+ }
- boolean isFullScreen() {
- return mFullScreen;
- }
+ boolean isFullScreen() {
+ return mFullScreen;
+ }
- void setFullScreen(Context context, boolean newValue) {
- mFullScreen = newValue;
+ void setFullScreen(Context context, boolean newValue) {
+ mFullScreen = newValue;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
- }
+ }
boolean isShowExtraKeys() {
return mShowExtraKeys;
@@ -92,74 +93,75 @@ final class TermuxPreferences {
}
int getFontSize() {
- return mFontSize;
- }
+ return mFontSize;
+ }
- void changeFontSize(Context context, boolean increase) {
- mFontSize += (increase ? 1 : -1) * 2;
- mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
+ void changeFontSize(Context context, boolean increase) {
+ mFontSize += (increase ? 1 : -1) * 2;
+ mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
- }
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
+ }
- static void storeCurrentSession(Context context, TerminalSession session) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).commit();
- }
+ static void storeCurrentSession(Context context, TerminalSession session) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).commit();
+ }
- static TerminalSession getCurrentSession(TermuxActivity context) {
- String sessionHandle = PreferenceManager.getDefaultSharedPreferences(context).getString(TermuxPreferences.CURRENT_SESSION_KEY, "");
- for (int i = 0, len = context.mTermService.getSessions().size(); i < len; i++) {
- TerminalSession session = context.mTermService.getSessions().get(i);
- if (session.mHandle.equals(sessionHandle)) return session;
- }
- return null;
- }
+ static TerminalSession getCurrentSession(TermuxActivity context) {
+ String sessionHandle = PreferenceManager.getDefaultSharedPreferences(context).getString(TermuxPreferences.CURRENT_SESSION_KEY, "");
+ for (int i = 0, len = context.mTermService.getSessions().size(); i < len; i++) {
+ TerminalSession session = context.mTermService.getSessions().get(i);
+ if (session.mHandle.equals(sessionHandle)) return session;
+ }
+ return null;
+ }
- public static boolean isShowWelcomeDialog(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
- }
+ public static boolean isShowWelcomeDialog(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
+ }
- public static void disableWelcomeDialog(Context context) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
- }
+ public static void disableWelcomeDialog(Context context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
+ }
- public void reloadFromProperties(Context context) {
- try {
- File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
- if (!propsFile.exists()) propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
+ public void reloadFromProperties(Context context) {
+ try {
+ File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
+ if (!propsFile.exists())
+ propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
- Properties props = new Properties();
- if (propsFile.isFile() && propsFile.canRead()) {
- try (FileInputStream in = new FileInputStream(propsFile)) {
- props.load(in);
- }
- }
+ Properties props = new Properties();
+ if (propsFile.isFile() && propsFile.canRead()) {
+ try (FileInputStream in = new FileInputStream(propsFile)) {
+ props.load(in);
+ }
+ }
- switch (props.getProperty("bell-character", "vibrate")) {
- case "beep":
- mBellBehaviour = BELL_BEEP;
- break;
- case "ignore":
- mBellBehaviour = BELL_IGNORE;
- break;
- default: // "vibrate".
- mBellBehaviour = BELL_VIBRATE;
- break;
- }
+ switch (props.getProperty("bell-character", "vibrate")) {
+ case "beep":
+ mBellBehaviour = BELL_BEEP;
+ break;
+ case "ignore":
+ mBellBehaviour = BELL_IGNORE;
+ break;
+ default: // "vibrate".
+ mBellBehaviour = BELL_VIBRATE;
+ break;
+ }
- mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
+ mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
shortcuts.clear();
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props);
- } catch (Exception e) {
- Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
- Log.e("termux", "Error loading props", e);
- }
- }
+ } catch (Exception e) {
+ Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
+ Log.e("termux", "Error loading props", e);
+ }
+ }
public static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
public static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java
index 05714619..34e21bba 100644
--- a/app/src/main/java/com/termux/app/TermuxService.java
+++ b/app/src/main/java/com/termux/app/TermuxService.java
@@ -1,15 +1,5 @@
package com.termux.app;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.termux.R;
-import com.termux.terminal.EmulatorDebug;
-import com.termux.terminal.TerminalSession;
-import com.termux.terminal.TerminalSession.SessionChangedCallback;
-
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
@@ -26,331 +16,342 @@ import android.os.PowerManager;
import android.util.Log;
import android.widget.ArrayAdapter;
+import com.termux.R;
+import com.termux.terminal.EmulatorDebug;
+import com.termux.terminal.TerminalSession;
+import com.termux.terminal.TerminalSession.SessionChangedCallback;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A service holding a list of terminal sessions, {@link #mTerminalSessions}, 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.
- *
+ *
* In order to keep both terminal sessions and spawned processes (who may outlive the terminal sessions) alive as long
* as wanted by the user this service is a foreground service, {@link Service#startForeground(int, Notification)}.
- *
+ *
* Optionally may hold a wake and a wifi lock, in which case that is shown in the notification - see
* {@link #buildNotification()}.
*/
public final class TermuxService extends Service implements SessionChangedCallback {
- /** Note that this is a symlink on the Android M preview. */
- @SuppressLint("SdCardPath")
- public static final String FILES_PATH = "/data/data/com.termux/files";
- public static final String PREFIX_PATH = FILES_PATH + "/usr";
- public static final String HOME_PATH = FILES_PATH + "/home";
+ /** Note that this is a symlink on the Android M preview. */
+ @SuppressLint("SdCardPath")
+ public static final String FILES_PATH = "/data/data/com.termux/files";
+ public static final String PREFIX_PATH = FILES_PATH + "/usr";
+ public static final String HOME_PATH = FILES_PATH + "/home";
- private static final int NOTIFICATION_ID = 1337;
+ private static final int NOTIFICATION_ID = 1337;
- /** Intent action to stop the service. */
- private static final String ACTION_STOP_SERVICE = "com.termux.service_stop";
- /** Intent action to toggle the wake lock, {@link #mWakeLock}, which this service may hold. */
- private static final String ACTION_LOCK_WAKE = "com.termux.service_toggle_wake_lock";
- /** Intent action to toggle the wifi lock, {@link #mWifiLock}, which this service may hold. */
- private static final String ACTION_LOCK_WIFI = "com.termux.service_toggle_wifi_lock";
- /** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
- public static final String ACTION_EXECUTE = "com.termux.service_execute";
+ /** Intent action to stop the service. */
+ private static final String ACTION_STOP_SERVICE = "com.termux.service_stop";
+ /** Intent action to toggle the wake lock, {@link #mWakeLock}, which this service may hold. */
+ private static final String ACTION_LOCK_WAKE = "com.termux.service_toggle_wake_lock";
+ /** Intent action to toggle the wifi lock, {@link #mWifiLock}, which this service may hold. */
+ private static final String ACTION_LOCK_WIFI = "com.termux.service_toggle_wifi_lock";
+ /** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
+ public static final String ACTION_EXECUTE = "com.termux.service_execute";
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
- /** This service is only bound from inside the same process and never uses IPC. */
- class LocalBinder extends Binder {
- public final TermuxService service = TermuxService.this;
- }
+ /** This service is only bound from inside the same process and never uses IPC. */
+ class LocalBinder extends Binder {
+ public final TermuxService service = TermuxService.this;
+ }
- private final IBinder mBinder = new LocalBinder();
+ private final IBinder mBinder = new LocalBinder();
- /**
- * The terminal sessions which this service manages.
- *
- * Note that this list is observed by {@link TermuxActivity#mListViewAdapter}, so any changes must be made on the UI
- * thread and followed by a call to {@link ArrayAdapter#notifyDataSetChanged()} }.
- */
- final List mTerminalSessions = new ArrayList<>();
+ /**
+ * The terminal sessions which this service manages.
+ *
+ * Note that this list is observed by {@link TermuxActivity#mListViewAdapter}, so any changes must be made on the UI
+ * thread and followed by a call to {@link ArrayAdapter#notifyDataSetChanged()} }.
+ */
+ final List mTerminalSessions = new ArrayList<>();
- /** Note that the service may often outlive the activity, so need to clear this reference. */
- SessionChangedCallback mSessionChangeCallback;
+ /** Note that the service may often outlive the activity, so need to clear this reference. */
+ SessionChangedCallback mSessionChangeCallback;
- private PowerManager.WakeLock mWakeLock;
- private WifiManager.WifiLock mWifiLock;
+ private PowerManager.WakeLock mWakeLock;
+ private WifiManager.WifiLock mWifiLock;
- /** If the user has executed the {@link #ACTION_STOP_SERVICE} intent. */
- boolean mWantsToStop = false;
+ /** If the user has executed the {@link #ACTION_STOP_SERVICE} intent. */
+ boolean mWantsToStop = false;
- @SuppressLint("Wakelock")
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- String action = intent.getAction();
- if (ACTION_STOP_SERVICE.equals(action)) {
- mWantsToStop = true;
- for (int i = 0; i < mTerminalSessions.size(); i++)
- mTerminalSessions.get(i).finishIfRunning();
- stopSelf();
- } else if (ACTION_LOCK_WAKE.equals(action)) {
- if (mWakeLock == null) {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
- mWakeLock.acquire();
- } else {
- mWakeLock.release();
- mWakeLock = null;
- }
- updateNotification();
- } else if (ACTION_LOCK_WIFI.equals(action)) {
- if (mWifiLock == null) {
- WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
- mWifiLock.acquire();
- } else {
- mWifiLock.release();
- mWifiLock = null;
- }
- updateNotification();
- } else if (ACTION_EXECUTE.equals(action)) {
- Uri executableUri = intent.getData();
- String executablePath = (executableUri == null ? null : executableUri.getPath());
- String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
- String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
- TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
+ @SuppressLint("Wakelock")
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String action = intent.getAction();
+ if (ACTION_STOP_SERVICE.equals(action)) {
+ mWantsToStop = true;
+ for (int i = 0; i < mTerminalSessions.size(); i++)
+ mTerminalSessions.get(i).finishIfRunning();
+ stopSelf();
+ } else if (ACTION_LOCK_WAKE.equals(action)) {
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
+ mWakeLock.acquire();
+ } else {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+ updateNotification();
+ } else if (ACTION_LOCK_WIFI.equals(action)) {
+ if (mWifiLock == null) {
+ WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
+ mWifiLock.acquire();
+ } else {
+ mWifiLock.release();
+ mWifiLock = null;
+ }
+ updateNotification();
+ } else if (ACTION_EXECUTE.equals(action)) {
+ Uri executableUri = intent.getData();
+ String executablePath = (executableUri == null ? null : executableUri.getPath());
+ String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
+ String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
+ TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
- // Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
- if (executablePath != null) {
- int lastSlash = executablePath.lastIndexOf('/');
- String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
- name = name.replace('-', ' ');
- newSession.mSessionName = name;
- }
+ // Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
+ if (executablePath != null) {
+ int lastSlash = executablePath.lastIndexOf('/');
+ String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
+ name = name.replace('-', ' ');
+ newSession.mSessionName = name;
+ }
- // Make the newly created session the current one to be displayed:
- TermuxPreferences.storeCurrentSession(this, newSession);
+ // Make the newly created session the current one to be displayed:
+ TermuxPreferences.storeCurrentSession(this, newSession);
- // Launch the main Termux app, which will now show to current session:
- startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- } else if (action != null) {
- Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
- }
+ // Launch the main Termux app, which will now show to current session:
+ startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ } else if (action != null) {
+ Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
+ }
- // If this service really do get killed, there is no point restarting it automatically - let the user do on next
- // start of {@link Term):
- return Service.START_NOT_STICKY;
- }
+ // If this service really do get killed, there is no point restarting it automatically - let the user do on next
+ // start of {@link Term):
+ return Service.START_NOT_STICKY;
+ }
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
- @Override
- public void onCreate() {
- startForeground(NOTIFICATION_ID, buildNotification());
- }
+ @Override
+ public void onCreate() {
+ startForeground(NOTIFICATION_ID, buildNotification());
+ }
- /** Update the shown foreground service notification after making any changes that affect it. */
- private void updateNotification() {
- if (mWakeLock == null && mWifiLock == null && getSessions().isEmpty()) {
- // Exit if we are updating after the user disabled all locks with no sessions.
- stopSelf();
- } else {
- ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
- }
- }
+ /** Update the shown foreground service notification after making any changes that affect it. */
+ private void updateNotification() {
+ if (mWakeLock == null && mWifiLock == null && getSessions().isEmpty()) {
+ // Exit if we are updating after the user disabled all locks with no sessions.
+ stopSelf();
+ } else {
+ ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
+ }
+ }
- private Notification buildNotification() {
- Intent notifyIntent = new Intent(this, TermuxActivity.class);
- // PendingIntent#getActivity(): "Note that the activity will be started outside of the context of an existing
- // activity, so you must use the Intent.FLAG_ACTIVITY_NEW_TASK launch flag in the Intent":
- notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
+ private Notification buildNotification() {
+ Intent notifyIntent = new Intent(this, TermuxActivity.class);
+ // PendingIntent#getActivity(): "Note that the activity will be started outside of the context of an existing
+ // activity, so you must use the Intent.FLAG_ACTIVITY_NEW_TASK launch flag in the Intent":
+ notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
- int sessionCount = mTerminalSessions.size();
- String contentText = sessionCount + " terminal session" + (sessionCount == 1 ? "" : "s");
+ int sessionCount = mTerminalSessions.size();
+ String contentText = sessionCount + " terminal session" + (sessionCount == 1 ? "" : "s");
- boolean wakeLockHeld = mWakeLock != null;
- boolean wifiLockHeld = mWifiLock != null;
- if (wakeLockHeld && wifiLockHeld) {
- contentText += " (wake&wifi lock held)";
- } else if (wakeLockHeld) {
- contentText += " (wake lock held)";
- } else if (wifiLockHeld) {
- contentText += " (wifi lock held)";
- }
+ boolean wakeLockHeld = mWakeLock != null;
+ boolean wifiLockHeld = mWifiLock != null;
+ if (wakeLockHeld && wifiLockHeld) {
+ contentText += " (wake&wifi lock held)";
+ } else if (wakeLockHeld) {
+ contentText += " (wake lock held)";
+ } else if (wifiLockHeld) {
+ contentText += " (wifi lock held)";
+ }
- Notification.Builder builder = new Notification.Builder(this);
- builder.setContentTitle(getText(R.string.application_name));
- builder.setContentText(contentText);
- builder.setSmallIcon(R.drawable.ic_service_notification);
- builder.setContentIntent(pendingIntent);
- builder.setOngoing(true);
+ Notification.Builder builder = new Notification.Builder(this);
+ builder.setContentTitle(getText(R.string.application_name));
+ builder.setContentText(contentText);
+ builder.setSmallIcon(R.drawable.ic_service_notification);
+ builder.setContentIntent(pendingIntent);
+ builder.setOngoing(true);
- // If holding a wake or wifi lock consider the notification of high priority since it's using power,
- // otherwise use a minimal priority since this is just a background service notification:
- builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
+ // If holding a wake or wifi lock consider the notification of high priority since it's using power,
+ // otherwise use a minimal priority since this is just a background service notification:
+ builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
- // No need to show a timestamp:
- builder.setShowWhen(false);
+ // No need to show a timestamp:
+ builder.setShowWhen(false);
- // Background color for small notification icon:
- builder.setColor(0xFF000000);
+ // Background color for small notification icon:
+ builder.setColor(0xFF000000);
- Resources res = getResources();
- Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
- builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
+ Resources res = getResources();
+ Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
+ builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
- Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WAKE);
- builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wakelock),
- PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
+ Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WAKE);
+ builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wakelock),
+ PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
- Intent toggleWifiLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WIFI);
- builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wifilock),
- PendingIntent.getService(this, 0, toggleWifiLockIntent, 0));
+ Intent toggleWifiLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WIFI);
+ builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wifilock),
+ PendingIntent.getService(this, 0, toggleWifiLockIntent, 0));
- return builder.build();
- }
+ return builder.build();
+ }
- @Override
- public void onDestroy() {
- if (mWakeLock != null) mWakeLock.release();
- if (mWifiLock != null) mWifiLock.release();
+ @Override
+ public void onDestroy() {
+ if (mWakeLock != null) mWakeLock.release();
+ if (mWifiLock != null) mWifiLock.release();
- stopForeground(true);
+ stopForeground(true);
- for (int i = 0; i < mTerminalSessions.size(); i++)
- mTerminalSessions.get(i).finishIfRunning();
- mTerminalSessions.clear();
- }
+ for (int i = 0; i < mTerminalSessions.size(); i++)
+ mTerminalSessions.get(i).finishIfRunning();
+ mTerminalSessions.clear();
+ }
- public List getSessions() {
- return mTerminalSessions;
- }
+ public List getSessions() {
+ return mTerminalSessions;
+ }
- TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
- new File(HOME_PATH).mkdirs();
+ TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
+ new File(HOME_PATH).mkdirs();
- if (cwd == null) cwd = HOME_PATH;
+ if (cwd == null) cwd = HOME_PATH;
- final String termEnv = "TERM=xterm-256color";
- final String homeEnv = "HOME=" + HOME_PATH;
- final String prefixEnv = "PREFIX=" + PREFIX_PATH;
- final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
- final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
+ final String termEnv = "TERM=xterm-256color";
+ final String homeEnv = "HOME=" + HOME_PATH;
+ final String prefixEnv = "PREFIX=" + PREFIX_PATH;
+ final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
+ final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
- String[] env;
- if (failSafe) {
+ String[] env;
+ if (failSafe) {
// Keep the default path so that system binaries can be used in the failsafe session.
final String pathEnv = "PATH=" + System.getenv("PATH");
- env = new String[] { termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv };
- } else {
- final String ps1Env = "PS1=$ ";
- final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
- final String langEnv = "LANG=en_US.UTF-8";
- final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
- final String pwdEnv = "PWD=" + cwd;
+ env = new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
+ } else {
+ final String ps1Env = "PS1=$ ";
+ final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
+ final String langEnv = "LANG=en_US.UTF-8";
+ final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
+ final String pwdEnv = "PWD=" + cwd;
- env = new String[] { termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv };
- }
+ env = new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
+ }
- String shellName;
- if (executablePath == null) {
- File shell = new File(HOME_PATH, ".termux/shell");
- if (shell.exists()) {
- try {
- File canonicalFile = shell.getCanonicalFile();
- if (canonicalFile.isFile() && canonicalFile.canExecute()) {
- executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
- } else {
- Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
- }
- } catch (IOException e) {
- Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
- }
- }
+ String shellName;
+ if (executablePath == null) {
+ File shell = new File(HOME_PATH, ".termux/shell");
+ if (shell.exists()) {
+ try {
+ File canonicalFile = shell.getCanonicalFile();
+ if (canonicalFile.isFile() && canonicalFile.canExecute()) {
+ executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
+ } else {
+ Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
+ }
+ }
- if (executablePath == null) {
- // Try bash, zsh and ash in that order:
- for (String shellBinary : new String[] { "bash", "zsh", "ash" }) {
- File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
- if (shellFile.canExecute()) {
- executablePath = shellFile.getAbsolutePath();
- break;
- }
- }
- }
+ if (executablePath == null) {
+ // Try bash, zsh and ash in that order:
+ for (String shellBinary : new String[]{"bash", "zsh", "ash"}) {
+ File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
+ if (shellFile.canExecute()) {
+ executablePath = shellFile.getAbsolutePath();
+ break;
+ }
+ }
+ }
- if (executablePath == null) {
- // Fall back to system shell as last resort:
- executablePath = "/system/bin/sh";
- }
+ if (executablePath == null) {
+ // Fall back to system shell as last resort:
+ executablePath = "/system/bin/sh";
+ }
- String[] parts = executablePath.split("/");
- shellName = "-" + parts[parts.length - 1];
- } else {
- int lastSlashIndex = executablePath.lastIndexOf('/');
- shellName = lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1);
- }
+ String[] parts = executablePath.split("/");
+ shellName = "-" + parts[parts.length - 1];
+ } else {
+ int lastSlashIndex = executablePath.lastIndexOf('/');
+ shellName = lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1);
+ }
- String[] args;
- if (arguments == null) {
- args = new String[] { shellName };
- } else {
- args = new String[arguments.length + 1];
- args[0] = shellName;
+ String[] args;
+ if (arguments == null) {
+ args = new String[]{shellName};
+ } else {
+ args = new String[arguments.length + 1];
+ args[0] = shellName;
- System.arraycopy(arguments, 0, args, 1, arguments.length);
- }
+ System.arraycopy(arguments, 0, args, 1, arguments.length);
+ }
- TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
- mTerminalSessions.add(session);
- updateNotification();
- return session;
- }
+ TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
+ mTerminalSessions.add(session);
+ updateNotification();
+ return session;
+ }
- public int removeTermSession(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.
- stopSelf();
- } else {
- updateNotification();
- }
- return indexOfRemoved;
- }
+ public int removeTermSession(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.
+ stopSelf();
+ } else {
+ updateNotification();
+ }
+ return indexOfRemoved;
+ }
- @Override
- public void onTitleChanged(TerminalSession changedSession) {
- if (mSessionChangeCallback != null) mSessionChangeCallback.onTitleChanged(changedSession);
- }
+ @Override
+ public void onTitleChanged(TerminalSession changedSession) {
+ if (mSessionChangeCallback != null) mSessionChangeCallback.onTitleChanged(changedSession);
+ }
- @Override
- public void onSessionFinished(final TerminalSession finishedSession) {
- if (mSessionChangeCallback != null) mSessionChangeCallback.onSessionFinished(finishedSession);
- }
+ @Override
+ public void onSessionFinished(final TerminalSession finishedSession) {
+ if (mSessionChangeCallback != null)
+ mSessionChangeCallback.onSessionFinished(finishedSession);
+ }
- @Override
- public void onTextChanged(TerminalSession changedSession) {
- if (mSessionChangeCallback != null) mSessionChangeCallback.onTextChanged(changedSession);
- }
+ @Override
+ public void onTextChanged(TerminalSession changedSession) {
+ if (mSessionChangeCallback != null) mSessionChangeCallback.onTextChanged(changedSession);
+ }
- @Override
- public void onClipboardText(TerminalSession session, String text) {
- if (mSessionChangeCallback != null) mSessionChangeCallback.onClipboardText(session, text);
- }
+ @Override
+ public void onClipboardText(TerminalSession session, String text) {
+ if (mSessionChangeCallback != null) mSessionChangeCallback.onClipboardText(session, text);
+ }
- @Override
- public void onBell(TerminalSession session) {
- if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
- }
+ @Override
+ public void onBell(TerminalSession session) {
+ if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
+ }
@Override
public void onColorsChanged(TerminalSession session) {
diff --git a/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java b/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java
index 633a7025..a7c3448d 100644
--- a/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java
+++ b/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java
@@ -23,9 +23,9 @@ import java.util.LinkedList;
/**
* A document provider for the Storage Access Framework which exposes the files in the
* $HOME/ folder to other apps.
- *
+ *
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
- *
+ *
* "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
* support both of them simultaneously, your app will appear twice in the system picker UI,
* offering two different ways of accessing your stored data. This would be confusing for users."
@@ -172,7 +172,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
/**
* Get the document id given a file. This document id must be consistent across time as other
* applications may save the ID and use it to reference documents later.
- *
+ *
* The reverse of @{link #getFileForDocId}.
*/
private static String getDocIdForFile(File file) {
diff --git a/app/src/main/java/com/termux/terminal/ByteQueue.java b/app/src/main/java/com/termux/terminal/ByteQueue.java
index 1eb3e59d..adfdfa86 100644
--- a/app/src/main/java/com/termux/terminal/ByteQueue.java
+++ b/app/src/main/java/com/termux/terminal/ByteQueue.java
@@ -3,106 +3,106 @@ package com.termux.terminal;
/** A circular byte buffer allowing one producer and one consumer thread. */
final class ByteQueue {
- private final byte[] mBuffer;
- private int mHead;
- private int mStoredBytes;
- private boolean mOpen = true;
+ private final byte[] mBuffer;
+ private int mHead;
+ private int mStoredBytes;
+ private boolean mOpen = true;
- public ByteQueue(int size) {
- mBuffer = new byte[size];
- }
+ public ByteQueue(int size) {
+ mBuffer = new byte[size];
+ }
- public synchronized void close() {
- mOpen = false;
- notify();
- }
+ public synchronized void close() {
+ mOpen = false;
+ notify();
+ }
- public synchronized int read(byte[] buffer, boolean block) {
- while (mStoredBytes == 0 && mOpen) {
- if (block) {
- try {
- wait();
- } catch (InterruptedException e) {
- // Ignore.
- }
- } else {
- return 0;
- }
- }
- if (!mOpen) return -1;
+ public synchronized int read(byte[] buffer, boolean block) {
+ while (mStoredBytes == 0 && mOpen) {
+ if (block) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ } else {
+ return 0;
+ }
+ }
+ if (!mOpen) return -1;
- int totalRead = 0;
- int bufferLength = mBuffer.length;
- boolean wasFull = bufferLength == mStoredBytes;
- int length = buffer.length;
- int offset = 0;
- while (length > 0 && mStoredBytes > 0) {
- int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
- int bytesToCopy = Math.min(length, oneRun);
- System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
- mHead += bytesToCopy;
- if (mHead >= bufferLength) mHead = 0;
- mStoredBytes -= bytesToCopy;
- length -= bytesToCopy;
- offset += bytesToCopy;
- totalRead += bytesToCopy;
- }
- if (wasFull) notify();
- return totalRead;
- }
+ int totalRead = 0;
+ int bufferLength = mBuffer.length;
+ boolean wasFull = bufferLength == mStoredBytes;
+ int length = buffer.length;
+ int offset = 0;
+ while (length > 0 && mStoredBytes > 0) {
+ int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
+ int bytesToCopy = Math.min(length, oneRun);
+ System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
+ mHead += bytesToCopy;
+ if (mHead >= bufferLength) mHead = 0;
+ mStoredBytes -= bytesToCopy;
+ length -= bytesToCopy;
+ offset += bytesToCopy;
+ totalRead += bytesToCopy;
+ }
+ if (wasFull) notify();
+ return totalRead;
+ }
- /**
- * Attempt to write the specified portion of the provided buffer to the queue.
- *
- * Returns whether the output was totally written, false if it was closed before.
- */
- public boolean write(byte[] buffer, int offset, int lengthToWrite) {
- if (lengthToWrite + offset > buffer.length) {
- throw new IllegalArgumentException("length + offset > buffer.length");
- } else if (lengthToWrite <= 0) {
- throw new IllegalArgumentException("length <= 0");
- }
+ /**
+ * Attempt to write the specified portion of the provided buffer to the queue.
+ *
+ * Returns whether the output was totally written, false if it was closed before.
+ */
+ public boolean write(byte[] buffer, int offset, int lengthToWrite) {
+ if (lengthToWrite + offset > buffer.length) {
+ throw new IllegalArgumentException("length + offset > buffer.length");
+ } else if (lengthToWrite <= 0) {
+ throw new IllegalArgumentException("length <= 0");
+ }
- final int bufferLength = mBuffer.length;
+ final int bufferLength = mBuffer.length;
- synchronized (this) {
- while (lengthToWrite > 0) {
- while (bufferLength == mStoredBytes && mOpen) {
- try {
- wait();
- } catch (InterruptedException e) {
- // Ignore.
- }
- }
- if (!mOpen) return false;
- final boolean wasEmpty = mStoredBytes == 0;
- int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes);
- lengthToWrite -= bytesToWriteBeforeWaiting;
+ synchronized (this) {
+ while (lengthToWrite > 0) {
+ while (bufferLength == mStoredBytes && mOpen) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+ if (!mOpen) return false;
+ final boolean wasEmpty = mStoredBytes == 0;
+ int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes);
+ lengthToWrite -= bytesToWriteBeforeWaiting;
- while (bytesToWriteBeforeWaiting > 0) {
- int tail = mHead + mStoredBytes;
- int oneRun;
- if (tail >= bufferLength) {
- // Buffer: [.............]
- // ________________H_______T
- // =>
- // Buffer: [.............]
- // ___________T____H
- // onRun= _____----_
- tail = tail - bufferLength;
- oneRun = mHead - tail;
- } else {
- oneRun = bufferLength - tail;
- }
- int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting);
- System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
- offset += bytesToCopy;
- bytesToWriteBeforeWaiting -= bytesToCopy;
- mStoredBytes += bytesToCopy;
- }
- if (wasEmpty) notify();
- }
- }
- return true;
- }
+ while (bytesToWriteBeforeWaiting > 0) {
+ int tail = mHead + mStoredBytes;
+ int oneRun;
+ if (tail >= bufferLength) {
+ // Buffer: [.............]
+ // ________________H_______T
+ // =>
+ // Buffer: [.............]
+ // ___________T____H
+ // onRun= _____----_
+ tail = tail - bufferLength;
+ oneRun = mHead - tail;
+ } else {
+ oneRun = bufferLength - tail;
+ }
+ int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting);
+ System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
+ offset += bytesToCopy;
+ bytesToWriteBeforeWaiting -= bytesToCopy;
+ mStoredBytes += bytesToCopy;
+ }
+ if (wasEmpty) notify();
+ }
+ }
+ return true;
+ }
}
diff --git a/app/src/main/java/com/termux/terminal/EmulatorDebug.java b/app/src/main/java/com/termux/terminal/EmulatorDebug.java
index 213657a9..f9cef418 100644
--- a/app/src/main/java/com/termux/terminal/EmulatorDebug.java
+++ b/app/src/main/java/com/termux/terminal/EmulatorDebug.java
@@ -4,7 +4,7 @@ import android.util.Log;
public final class EmulatorDebug {
- /** The tag to use with {@link Log}. */
- public static final String LOG_TAG = "termux";
+ /** The tag to use with {@link Log}. */
+ public static final String LOG_TAG = "termux";
}
diff --git a/app/src/main/java/com/termux/terminal/JNI.java b/app/src/main/java/com/termux/terminal/JNI.java
index ed3bc570..99229a65 100644
--- a/app/src/main/java/com/termux/terminal/JNI.java
+++ b/app/src/main/java/com/termux/terminal/JNI.java
@@ -5,42 +5,37 @@ package com.termux.terminal;
*/
final class JNI {
- static {
- System.loadLibrary("termux");
- }
+ static {
+ System.loadLibrary("termux");
+ }
- /**
- * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
- * subprocess.
- *
- * Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
- *
- * @param cmd
- * The command to execute
- * @param cwd
- * The current working directory for the executed command
- * @param args
- * An array of arguments to the command
- * @param envVars
- * An array of strings of the form "VAR=value" to be added to the environment of the process
- * @param processId
- * A one-element array to which the process ID of the started process will be written.
- * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
- * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
- */
- public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
+ /**
+ * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
+ * subprocess.
+ *
+ * Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
+ *
+ * @param cmd The command to execute
+ * @param cwd The current working directory for the executed command
+ * @param args An array of arguments to the command
+ * @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process
+ * @param processId A one-element array to which the process ID of the started process will be written.
+ * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
+ * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
+ */
+ public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
- /** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */
- public static native void setPtyWindowSize(int fd, int rows, int cols);
+ /** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */
+ public static native void setPtyWindowSize(int fd, int rows, int cols);
- /**
- * Causes the calling thread to wait for the process associated with the receiver to finish executing.
- *
- * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated.
- */
- public static native int waitFor(int processId);
+ /**
+ * Causes the calling thread to wait for the process associated with the receiver to finish executing.
+ *
+ * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated.
+ */
+ public static native int waitFor(int processId);
- /** Close a file descriptor through the close(2) system call. */
- public static native void close(int fileDescriptor);
+ /** Close a file descriptor through the close(2) system call. */
+ public static native void close(int fileDescriptor);
}
diff --git a/app/src/main/java/com/termux/terminal/KeyHandler.java b/app/src/main/java/com/termux/terminal/KeyHandler.java
index 01451376..35bad649 100644
--- a/app/src/main/java/com/termux/terminal/KeyHandler.java
+++ b/app/src/main/java/com/termux/terminal/KeyHandler.java
@@ -1,5 +1,10 @@
package com.termux.terminal;
+import android.view.KeyEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+
import static android.view.KeyEvent.KEYCODE_BREAK;
import static android.view.KeyEvent.KEYCODE_DEL;
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
@@ -22,6 +27,7 @@ import static android.view.KeyEvent.KEYCODE_F7;
import static android.view.KeyEvent.KEYCODE_F8;
import static android.view.KeyEvent.KEYCODE_F9;
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
+import static android.view.KeyEvent.KEYCODE_HOME;
import static android.view.KeyEvent.KEYCODE_INSERT;
import static android.view.KeyEvent.KEYCODE_MOVE_END;
import static android.view.KeyEvent.KEYCODE_NUMPAD_0;
@@ -47,264 +53,259 @@ import static android.view.KeyEvent.KEYCODE_PAGE_DOWN;
import static android.view.KeyEvent.KEYCODE_PAGE_UP;
import static android.view.KeyEvent.KEYCODE_SYSRQ;
import static android.view.KeyEvent.KEYCODE_TAB;
-import static android.view.KeyEvent.KEYCODE_HOME;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import android.view.KeyEvent;
public final class KeyHandler {
- public static final int KEYMOD_ALT = 0x80000000;
- public static final int KEYMOD_CTRL = 0x40000000;
- public static final int KEYMOD_SHIFT = 0x20000000;
+ public static final int KEYMOD_ALT = 0x80000000;
+ public static final int KEYMOD_CTRL = 0x40000000;
+ public static final int KEYMOD_SHIFT = 0x20000000;
- private static final Map TERMCAP_TO_KEYCODE = new HashMap<>();
- static {
- // terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
- // termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
- TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
- TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_HOME); // Shifted home
- TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
- TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
+ private static final Map TERMCAP_TO_KEYCODE = new HashMap<>();
- TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1);
- TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
- TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
- TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
- TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
- TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
- TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
- TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
- TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
- TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
- TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
- TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
- TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
- TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
- TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
- TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
- TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
- TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
- TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
- TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
- TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
- TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
- TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
- TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12);
+ static {
+ // terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
+ // termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
+ TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
+ TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_HOME); // Shifted home
+ TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
+ TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
- TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
+ TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1);
+ TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
+ TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
+ TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
+ TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
+ TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
+ TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
+ TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
+ TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
+ TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
+ TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
+ TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
+ TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
+ TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
+ TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
+ TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
+ TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
+ TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
+ TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
+ TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
+ TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
+ TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
+ TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
+ TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12);
- TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
- TERMCAP_TO_KEYCODE.put("kh", KeyEvent.KEYCODE_HOME);
- TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
- TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
+ TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
- // K1=Upper left of keypad:
- // t_K1 keypad home key
- // t_K3 keypad page-up key
- // t_K4 keypad end key
- // t_K5 keypad page-down key
- TERMCAP_TO_KEYCODE.put("K1", KeyEvent.KEYCODE_HOME);
- TERMCAP_TO_KEYCODE.put("K3", KeyEvent.KEYCODE_PAGE_UP);
- TERMCAP_TO_KEYCODE.put("K4", KeyEvent.KEYCODE_MOVE_END);
- TERMCAP_TO_KEYCODE.put("K5", KeyEvent.KEYCODE_PAGE_DOWN);
+ TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
+ TERMCAP_TO_KEYCODE.put("kh", KeyEvent.KEYCODE_HOME);
+ TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
+ TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
- TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
+ // K1=Upper left of keypad:
+ // t_K1 keypad home key
+ // t_K3 keypad page-up key
+ // t_K4 keypad end key
+ // t_K5 keypad page-down key
+ TERMCAP_TO_KEYCODE.put("K1", KeyEvent.KEYCODE_HOME);
+ TERMCAP_TO_KEYCODE.put("K3", KeyEvent.KEYCODE_PAGE_UP);
+ TERMCAP_TO_KEYCODE.put("K4", KeyEvent.KEYCODE_MOVE_END);
+ TERMCAP_TO_KEYCODE.put("K5", KeyEvent.KEYCODE_PAGE_DOWN);
- TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab
- TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key
- TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down
- TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
- TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
- TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
- TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN);
- TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key
- TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
+ TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
- TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
- TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER);
- }
+ TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab
+ TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key
+ TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down
+ TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
+ TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
+ TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
+ TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN);
+ TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key
+ TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
- static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) {
- Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap);
- if (keyCodeAndMod == null) return null;
- int keyCode = keyCodeAndMod;
- int keyMod = 0;
- if ((keyCode & KEYMOD_SHIFT) != 0) {
- keyMod |= KEYMOD_SHIFT;
- keyCode &= ~KEYMOD_SHIFT;
- }
- if ((keyCode & KEYMOD_CTRL) != 0) {
- keyMod |= KEYMOD_CTRL;
- keyCode &= ~KEYMOD_CTRL;
- }
- if ((keyCode & KEYMOD_ALT) != 0) {
- keyMod |= KEYMOD_ALT;
- keyCode &= ~KEYMOD_ALT;
- }
- return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
- }
+ TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
+ TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER);
+ }
- public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
- switch (keyCode) {
- case KEYCODE_DPAD_CENTER:
- return "\015";
+ static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) {
+ Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap);
+ if (keyCodeAndMod == null) return null;
+ int keyCode = keyCodeAndMod;
+ int keyMod = 0;
+ if ((keyCode & KEYMOD_SHIFT) != 0) {
+ keyMod |= KEYMOD_SHIFT;
+ keyCode &= ~KEYMOD_SHIFT;
+ }
+ if ((keyCode & KEYMOD_CTRL) != 0) {
+ keyMod |= KEYMOD_CTRL;
+ keyCode &= ~KEYMOD_CTRL;
+ }
+ if ((keyCode & KEYMOD_ALT) != 0) {
+ keyMod |= KEYMOD_ALT;
+ keyCode &= ~KEYMOD_ALT;
+ }
+ return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
+ }
- case KEYCODE_DPAD_UP:
- return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A');
- case KEYCODE_DPAD_DOWN:
- return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B');
- case KEYCODE_DPAD_RIGHT:
- return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
- case KEYCODE_DPAD_LEFT:
- return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
+ public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
+ switch (keyCode) {
+ case KEYCODE_DPAD_CENTER:
+ return "\015";
- case KeyEvent.KEYCODE_HOME:
- return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
- case KEYCODE_MOVE_END:
- return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
+ case KEYCODE_DPAD_UP:
+ return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A');
+ case KEYCODE_DPAD_DOWN:
+ return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B');
+ case KEYCODE_DPAD_RIGHT:
+ return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
+ case KEYCODE_DPAD_LEFT:
+ return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
- // An xterm can send function keys F1 to F4 in two modes: vt100 compatible or
- // not. Because Vim may not know what the xterm is sending, both types of keys
- // are recognized. The same happens for the and keys.
- // normal vt100 ~
- // t_k1 [11~ OP *-xterm*
- // t_k2 [12~ OQ *-xterm*
- // t_k3 [13~ OR *-xterm*
- // t_k4 [14~ OS *-xterm*
- // t_kh [7~ OH *-xterm*
- // t_@7 [4~ OF *-xterm*
- case KEYCODE_F1:
- return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P');
- case KEYCODE_F2:
- return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q');
- case KEYCODE_F3:
- return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R');
- case KEYCODE_F4:
- return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S');
- case KEYCODE_F5:
- return transformForModifiers("\033[15", keyMode, '~');
- case KEYCODE_F6:
- return transformForModifiers("\033[17", keyMode, '~');
- case KEYCODE_F7:
- return transformForModifiers("\033[18", keyMode, '~');
- case KEYCODE_F8:
- return transformForModifiers("\033[19", keyMode, '~');
- case KEYCODE_F9:
- return transformForModifiers("\033[20", keyMode, '~');
- case KEYCODE_F10:
- return transformForModifiers("\033[21", keyMode, '~');
- case KEYCODE_F11:
- return transformForModifiers("\033[23", keyMode, '~');
- case KEYCODE_F12:
- return transformForModifiers("\033[24", keyMode, '~');
+ case KeyEvent.KEYCODE_HOME:
+ return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
+ case KEYCODE_MOVE_END:
+ return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
- case KEYCODE_SYSRQ:
- return "\033[32~"; // Sys Request / Print
- // Is this Scroll lock? case Cancel: return "\033[33~";
- case KEYCODE_BREAK:
- return "\033[34~"; // Pause/Break
+ // An xterm can send function keys F1 to F4 in two modes: vt100 compatible or
+ // not. Because Vim may not know what the xterm is sending, both types of keys
+ // are recognized. The same happens for the and keys.
+ // normal vt100 ~
+ // t_k1 [11~ OP *-xterm*
+ // t_k2 [12~ OQ *-xterm*
+ // t_k3 [13~ OR *-xterm*
+ // t_k4 [14~ OS *-xterm*
+ // t_kh [7~ OH *-xterm*
+ // t_@7 [4~ OF *-xterm*
+ case KEYCODE_F1:
+ return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P');
+ case KEYCODE_F2:
+ return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q');
+ case KEYCODE_F3:
+ return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R');
+ case KEYCODE_F4:
+ return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S');
+ case KEYCODE_F5:
+ return transformForModifiers("\033[15", keyMode, '~');
+ case KEYCODE_F6:
+ return transformForModifiers("\033[17", keyMode, '~');
+ case KEYCODE_F7:
+ return transformForModifiers("\033[18", keyMode, '~');
+ case KEYCODE_F8:
+ return transformForModifiers("\033[19", keyMode, '~');
+ case KEYCODE_F9:
+ return transformForModifiers("\033[20", keyMode, '~');
+ case KEYCODE_F10:
+ return transformForModifiers("\033[21", keyMode, '~');
+ case KEYCODE_F11:
+ return transformForModifiers("\033[23", keyMode, '~');
+ case KEYCODE_F12:
+ return transformForModifiers("\033[24", keyMode, '~');
- case KEYCODE_ESCAPE:
- case KeyEvent.KEYCODE_BACK:
- return "\033";
+ case KEYCODE_SYSRQ:
+ return "\033[32~"; // Sys Request / Print
+ // Is this Scroll lock? case Cancel: return "\033[33~";
+ case KEYCODE_BREAK:
+ return "\033[34~"; // Pause/Break
- case KEYCODE_INSERT:
- return transformForModifiers("\033[2", keyMode, '~');
- case KEYCODE_FORWARD_DEL:
- return transformForModifiers("\033[3", keyMode, '~');
+ case KEYCODE_ESCAPE:
+ case KeyEvent.KEYCODE_BACK:
+ return "\033";
- case KEYCODE_NUMPAD_DOT:
- return keypadApplication ? "\033On" : "\033[3~";
+ case KEYCODE_INSERT:
+ return transformForModifiers("\033[2", keyMode, '~');
+ case KEYCODE_FORWARD_DEL:
+ return transformForModifiers("\033[3", keyMode, '~');
- case KEYCODE_PAGE_UP:
- return "\033[5~";
- case KEYCODE_PAGE_DOWN:
- return "\033[6~";
- case KEYCODE_DEL:
- // Yes, this needs to U+007F and not U+0008!
- return "\u007F";
- case KEYCODE_NUM_LOCK:
- return "\033OP";
+ case KEYCODE_NUMPAD_DOT:
+ return keypadApplication ? "\033On" : "\033[3~";
- case KeyEvent.KEYCODE_SPACE:
- // If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
- // combining accent to be written):
- return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
- case KEYCODE_TAB:
- // This is back-tab when shifted:
- return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z";
- case KEYCODE_ENTER:
- return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r";
+ case KEYCODE_PAGE_UP:
+ return "\033[5~";
+ case KEYCODE_PAGE_DOWN:
+ return "\033[6~";
+ case KEYCODE_DEL:
+ // Yes, this needs to U+007F and not U+0008!
+ return "\u007F";
+ case KEYCODE_NUM_LOCK:
+ return "\033OP";
- case KEYCODE_NUMPAD_ENTER:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n";
- case KEYCODE_NUMPAD_MULTIPLY:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*";
- case KEYCODE_NUMPAD_ADD:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
- case KEYCODE_NUMPAD_COMMA:
- return ",";
- case KEYCODE_NUMPAD_SUBTRACT:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
- case KEYCODE_NUMPAD_DIVIDE:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
- case KEYCODE_NUMPAD_0:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
- case KEYCODE_NUMPAD_1:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
- case KEYCODE_NUMPAD_2:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
- case KEYCODE_NUMPAD_3:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
- case KEYCODE_NUMPAD_4:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
- case KEYCODE_NUMPAD_5:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
- case KEYCODE_NUMPAD_6:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
- case KEYCODE_NUMPAD_7:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
- case KEYCODE_NUMPAD_8:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
- case KEYCODE_NUMPAD_9:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
- case KEYCODE_NUMPAD_EQUALS:
- return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
- }
+ case KeyEvent.KEYCODE_SPACE:
+ // If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
+ // combining accent to be written):
+ return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
+ case KEYCODE_TAB:
+ // This is back-tab when shifted:
+ return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z";
+ case KEYCODE_ENTER:
+ return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r";
- return null;
- }
+ case KEYCODE_NUMPAD_ENTER:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n";
+ case KEYCODE_NUMPAD_MULTIPLY:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*";
+ case KEYCODE_NUMPAD_ADD:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
+ case KEYCODE_NUMPAD_COMMA:
+ return ",";
+ case KEYCODE_NUMPAD_SUBTRACT:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
+ case KEYCODE_NUMPAD_DIVIDE:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
+ case KEYCODE_NUMPAD_0:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
+ case KEYCODE_NUMPAD_1:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
+ case KEYCODE_NUMPAD_2:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
+ case KEYCODE_NUMPAD_3:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
+ case KEYCODE_NUMPAD_4:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
+ case KEYCODE_NUMPAD_5:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
+ case KEYCODE_NUMPAD_6:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
+ case KEYCODE_NUMPAD_7:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
+ case KEYCODE_NUMPAD_8:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
+ case KEYCODE_NUMPAD_9:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
+ case KEYCODE_NUMPAD_EQUALS:
+ return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
+ }
- private static String transformForModifiers(String start, int keymod, char lastChar) {
- int modifier;
- switch (keymod) {
- case KEYMOD_SHIFT:
- modifier = 2;
- break;
- case KEYMOD_ALT:
- modifier = 3;
- break;
- case (KEYMOD_SHIFT | KEYMOD_ALT):
- modifier = 4;
- break;
- case KEYMOD_CTRL:
- modifier = 5;
- break;
- case KEYMOD_SHIFT | KEYMOD_CTRL:
- modifier = 6;
- break;
- case KEYMOD_ALT | KEYMOD_CTRL:
- modifier = 7;
- break;
- case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL:
- modifier = 8;
- break;
- default:
- return start + lastChar;
- }
- return start + (";" + modifier) + lastChar;
- }
+ return null;
+ }
+
+ private static String transformForModifiers(String start, int keymod, char lastChar) {
+ int modifier;
+ switch (keymod) {
+ case KEYMOD_SHIFT:
+ modifier = 2;
+ break;
+ case KEYMOD_ALT:
+ modifier = 3;
+ break;
+ case (KEYMOD_SHIFT | KEYMOD_ALT):
+ modifier = 4;
+ break;
+ case KEYMOD_CTRL:
+ modifier = 5;
+ break;
+ case KEYMOD_SHIFT | KEYMOD_CTRL:
+ modifier = 6;
+ break;
+ case KEYMOD_ALT | KEYMOD_CTRL:
+ modifier = 7;
+ break;
+ case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL:
+ modifier = 8;
+ break;
+ default:
+ return start + lastChar;
+ }
+ return start + (";" + modifier) + lastChar;
+ }
}
diff --git a/app/src/main/java/com/termux/terminal/TerminalBuffer.java b/app/src/main/java/com/termux/terminal/TerminalBuffer.java
index c499ee3d..b57a8d00 100644
--- a/app/src/main/java/com/termux/terminal/TerminalBuffer.java
+++ b/app/src/main/java/com/termux/terminal/TerminalBuffer.java
@@ -3,441 +3,425 @@ package com.termux.terminal;
/**
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
* history.
- *
+ *
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
*/
public final class TerminalBuffer {
- TerminalRow[] mLines;
- /** The length of {@link #mLines}. */
- int mTotalRows;
- /** The number of rows and columns visible on the screen. */
- int mScreenRows, mColumns;
- /** The number of rows kept in history. */
- private int mActiveTranscriptRows = 0;
- /** The index in the circular buffer where the visible screen starts. */
- private int mScreenFirstRow = 0;
+ TerminalRow[] mLines;
+ /** The length of {@link #mLines}. */
+ int mTotalRows;
+ /** The number of rows and columns visible on the screen. */
+ int mScreenRows, mColumns;
+ /** The number of rows kept in history. */
+ private int mActiveTranscriptRows = 0;
+ /** The index in the circular buffer where the visible screen starts. */
+ private int mScreenFirstRow = 0;
- /**
- * Create a transcript screen.
- *
- * @param columns
- * the width of the screen in characters.
- * @param totalRows
- * the height of the entire text area, in rows of text.
- * @param screenRows
- * the height of just the screen, not including the transcript that holds lines that have scrolled off
- * the top of the screen.
- */
- public TerminalBuffer(int columns, int totalRows, int screenRows) {
- mColumns = columns;
- mTotalRows = totalRows;
- mScreenRows = screenRows;
- mLines = new TerminalRow[totalRows];
+ /**
+ * Create a transcript screen.
+ *
+ * @param columns the width of the screen in characters.
+ * @param totalRows the height of the entire text area, in rows of text.
+ * @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off
+ * the top of the screen.
+ */
+ public TerminalBuffer(int columns, int totalRows, int screenRows) {
+ mColumns = columns;
+ mTotalRows = totalRows;
+ mScreenRows = screenRows;
+ mLines = new TerminalRow[totalRows];
- blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL);
- }
+ blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL);
+ }
- public String getTranscriptText() {
- return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
- }
+ public String getTranscriptText() {
+ return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
+ }
- public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
- final StringBuilder builder = new StringBuilder();
- final int columns = mColumns;
+ public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
+ final StringBuilder builder = new StringBuilder();
+ final int columns = mColumns;
- if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows();
- if (selY2 >= mScreenRows) selY2 = mScreenRows - 1;
+ if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows();
+ if (selY2 >= mScreenRows) selY2 = mScreenRows - 1;
- for (int row = selY1; row <= selY2; row++) {
- int x1 = (row == selY1) ? selX1 : 0;
- int x2;
- if (row == selY2) {
- x2 = selX2 + 1;
- if (x2 > columns) x2 = columns;
- } else {
- x2 = columns;
- }
- TerminalRow lineObject = mLines[externalToInternalRow(row)];
- int x1Index = lineObject.findStartOfColumn(x1);
- int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
- if (x2Index == x1Index) {
- // Selected the start of a wide character.
- x2Index = lineObject.findStartOfColumn(x2+1);
- }
- char[] line = lineObject.mText;
- int lastPrintingCharIndex = -1;
- int i;
- boolean rowLineWrap = getLineWrap(row);
- if (rowLineWrap && x2 == columns) {
- // If the line was wrapped, we shouldn't lose trailing space:
- lastPrintingCharIndex = x2Index - 1;
- } else {
- for (i = x1Index; i < x2Index; ++i) {
- char c = line[i];
- if (c != ' ') lastPrintingCharIndex = i;
- }
- }
- if (lastPrintingCharIndex != -1) builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
- if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
- }
- return builder.toString();
- }
+ for (int row = selY1; row <= selY2; row++) {
+ int x1 = (row == selY1) ? selX1 : 0;
+ int x2;
+ if (row == selY2) {
+ x2 = selX2 + 1;
+ if (x2 > columns) x2 = columns;
+ } else {
+ x2 = columns;
+ }
+ TerminalRow lineObject = mLines[externalToInternalRow(row)];
+ int x1Index = lineObject.findStartOfColumn(x1);
+ int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
+ if (x2Index == x1Index) {
+ // Selected the start of a wide character.
+ x2Index = lineObject.findStartOfColumn(x2 + 1);
+ }
+ char[] line = lineObject.mText;
+ int lastPrintingCharIndex = -1;
+ int i;
+ boolean rowLineWrap = getLineWrap(row);
+ if (rowLineWrap && x2 == columns) {
+ // If the line was wrapped, we shouldn't lose trailing space:
+ lastPrintingCharIndex = x2Index - 1;
+ } else {
+ for (i = x1Index; i < x2Index; ++i) {
+ char c = line[i];
+ if (c != ' ') lastPrintingCharIndex = i;
+ }
+ }
+ if (lastPrintingCharIndex != -1)
+ builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
+ if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
+ }
+ return builder.toString();
+ }
- public int getActiveTranscriptRows() {
- return mActiveTranscriptRows;
- }
+ public int getActiveTranscriptRows() {
+ return mActiveTranscriptRows;
+ }
- public int getActiveRows() {
- return mActiveTranscriptRows + mScreenRows;
- }
+ public int getActiveRows() {
+ return mActiveTranscriptRows + mScreenRows;
+ }
- /**
- * Convert a row value from the public external coordinate system to our internal private coordinate system.
- *
- *
- *
External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
- *
Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
- * mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
- *
- *
- * @param externalRow
- * a row in the external coordinate system.
- * @return The row corresponding to the input argument in the private coordinate system.
- */
- public int externalToInternalRow(int externalRow) {
- if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows)
- throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows);
- final int internalRow = mScreenFirstRow + externalRow;
- return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows);
- }
+ /**
+ * Convert a row value from the public external coordinate system to our internal private coordinate system.
+ *
+ *
+ *
External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
+ *
Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
+ * mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
+ *
+ *
+ * @param externalRow a row in the external coordinate system.
+ * @return The row corresponding to the input argument in the private coordinate system.
+ */
+ public int externalToInternalRow(int externalRow) {
+ if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows)
+ throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows);
+ final int internalRow = mScreenFirstRow + externalRow;
+ return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows);
+ }
- public void setLineWrap(int row) {
- mLines[externalToInternalRow(row)].mLineWrap = true;
- }
+ public void setLineWrap(int row) {
+ mLines[externalToInternalRow(row)].mLineWrap = true;
+ }
- public boolean getLineWrap(int row) {
- return mLines[externalToInternalRow(row)].mLineWrap;
- }
+ public boolean getLineWrap(int row) {
+ return mLines[externalToInternalRow(row)].mLineWrap;
+ }
public void clearLineWrap(int row) {
mLines[externalToInternalRow(row)].mLineWrap = false;
}
- /**
- * Resize the screen which this transcript backs. Currently, this only works if the number of columns does not
- * change or the rows expand (that is, it only works when shrinking the number of rows).
- *
- * @param newColumns
- * The number of columns the screen should have.
- * @param newRows
- * The number of rows the screen should have.
- * @param cursor
- * An int[2] containing the (column, row) cursor location.
- */
- public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, int currentStyle, boolean altScreen) {
- // newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
- if (newColumns == mColumns && newRows <= mTotalRows) {
- // Fast resize where just the rows changed.
- int shiftDownOfTopRow = mScreenRows - newRows;
- if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) {
- // Shrinking. Check if we can skip blank rows at bottom below cursor.
- for (int i = mScreenRows - 1; i > 0; i--) {
- if (cursor[1] >= i) break;
- int r = externalToInternalRow(i);
- if (mLines[r] == null || mLines[r].isBlank()) {
- if (--shiftDownOfTopRow == 0) break;
- }
- }
- } else if (shiftDownOfTopRow < 0) {
- // Negative shift down = expanding. Only move screen up if there is transcript to show:
- int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows);
- if (shiftDownOfTopRow != actualShift) {
- // The new lines revealed by the resizing are not all from the transcript. Blank the below ones.
- for (int i = 0; i < actualShift - shiftDownOfTopRow; i++)
- allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle);
- shiftDownOfTopRow = actualShift;
- }
- }
- mScreenFirstRow += shiftDownOfTopRow;
- mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows);
- mTotalRows = newTotalRows;
- mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow);
- cursor[1] -= shiftDownOfTopRow;
- mScreenRows = newRows;
- } else {
- // Copy away old state and update new:
- TerminalRow[] oldLines = mLines;
- mLines = new TerminalRow[newTotalRows];
- for (int i = 0; i < newTotalRows; i++)
- mLines[i] = new TerminalRow(newColumns, currentStyle);
+ /**
+ * Resize the screen which this transcript backs. Currently, this only works if the number of columns does not
+ * change or the rows expand (that is, it only works when shrinking the number of rows).
+ *
+ * @param newColumns The number of columns the screen should have.
+ * @param newRows The number of rows the screen should have.
+ * @param cursor An int[2] containing the (column, row) cursor location.
+ */
+ public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, int currentStyle, boolean altScreen) {
+ // newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
+ if (newColumns == mColumns && newRows <= mTotalRows) {
+ // Fast resize where just the rows changed.
+ int shiftDownOfTopRow = mScreenRows - newRows;
+ if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) {
+ // Shrinking. Check if we can skip blank rows at bottom below cursor.
+ for (int i = mScreenRows - 1; i > 0; i--) {
+ if (cursor[1] >= i) break;
+ int r = externalToInternalRow(i);
+ if (mLines[r] == null || mLines[r].isBlank()) {
+ if (--shiftDownOfTopRow == 0) break;
+ }
+ }
+ } else if (shiftDownOfTopRow < 0) {
+ // Negative shift down = expanding. Only move screen up if there is transcript to show:
+ int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows);
+ if (shiftDownOfTopRow != actualShift) {
+ // The new lines revealed by the resizing are not all from the transcript. Blank the below ones.
+ for (int i = 0; i < actualShift - shiftDownOfTopRow; i++)
+ allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle);
+ shiftDownOfTopRow = actualShift;
+ }
+ }
+ mScreenFirstRow += shiftDownOfTopRow;
+ mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows);
+ mTotalRows = newTotalRows;
+ mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow);
+ cursor[1] -= shiftDownOfTopRow;
+ mScreenRows = newRows;
+ } else {
+ // Copy away old state and update new:
+ TerminalRow[] oldLines = mLines;
+ mLines = new TerminalRow[newTotalRows];
+ for (int i = 0; i < newTotalRows; i++)
+ mLines[i] = new TerminalRow(newColumns, currentStyle);
- final int oldActiveTranscriptRows = mActiveTranscriptRows;
- final int oldScreenFirstRow = mScreenFirstRow;
- final int oldScreenRows = mScreenRows;
- final int oldTotalRows = mTotalRows;
- mTotalRows = newTotalRows;
- mScreenRows = newRows;
- mActiveTranscriptRows = mScreenFirstRow = 0;
- mColumns = newColumns;
+ final int oldActiveTranscriptRows = mActiveTranscriptRows;
+ final int oldScreenFirstRow = mScreenFirstRow;
+ final int oldScreenRows = mScreenRows;
+ final int oldTotalRows = mTotalRows;
+ mTotalRows = newTotalRows;
+ mScreenRows = newRows;
+ mActiveTranscriptRows = mScreenFirstRow = 0;
+ mColumns = newColumns;
- int newCursorRow = -1;
- int newCursorColumn = -1;
- int oldCursorRow = cursor[1];
- int oldCursorColumn = cursor[0];
- boolean newCursorPlaced = false;
+ int newCursorRow = -1;
+ int newCursorColumn = -1;
+ int oldCursorRow = cursor[1];
+ int oldCursorColumn = cursor[0];
+ boolean newCursorPlaced = false;
- int currentOutputExternalRow = 0;
- int currentOutputExternalColumn = 0;
+ int currentOutputExternalRow = 0;
+ int currentOutputExternalColumn = 0;
- // Loop over every character in the initial state.
- // Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we
- // keep track how many blank lines we have skipped if we later on find a non-blank line.
- int skippedBlankLines = 0;
- for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) {
- // Do what externalToInternalRow() does but for the old state:
- int internalOldRow = oldScreenFirstRow + externalOldRow;
- internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows);
+ // Loop over every character in the initial state.
+ // Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we
+ // keep track how many blank lines we have skipped if we later on find a non-blank line.
+ int skippedBlankLines = 0;
+ for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) {
+ // Do what externalToInternalRow() does but for the old state:
+ int internalOldRow = oldScreenFirstRow + externalOldRow;
+ internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows);
- TerminalRow oldLine = oldLines[internalOldRow];
- boolean cursorAtThisRow = externalOldRow == oldCursorRow;
- // The cursor may only be on a non-null line, which we should not skip:
- if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {
- skippedBlankLines++;
- continue;
- } else if (skippedBlankLines > 0) {
- // After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines.
- for (int i = 0; i < skippedBlankLines; i++) {
- if (currentOutputExternalRow == mScreenRows - 1) {
- scrollDownOneLine(0, mScreenRows, currentStyle);
- } else {
- currentOutputExternalRow++;
- }
- currentOutputExternalColumn = 0;
- }
- skippedBlankLines = 0;
- }
+ TerminalRow oldLine = oldLines[internalOldRow];
+ boolean cursorAtThisRow = externalOldRow == oldCursorRow;
+ // The cursor may only be on a non-null line, which we should not skip:
+ if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {
+ skippedBlankLines++;
+ continue;
+ } else if (skippedBlankLines > 0) {
+ // After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines.
+ for (int i = 0; i < skippedBlankLines; i++) {
+ if (currentOutputExternalRow == mScreenRows - 1) {
+ scrollDownOneLine(0, mScreenRows, currentStyle);
+ } else {
+ currentOutputExternalRow++;
+ }
+ currentOutputExternalColumn = 0;
+ }
+ skippedBlankLines = 0;
+ }
- int lastNonSpaceIndex = 0;
- boolean justToCursor = false;
- if (cursorAtThisRow || oldLine.mLineWrap) {
- // Take the whole line, either because of cursor on it, or if line wrapping.
- lastNonSpaceIndex = oldLine.getSpaceUsed();
- if (cursorAtThisRow) justToCursor = true;
- } else {
- for (int i = 0; i < oldLine.getSpaceUsed(); i++)
- // NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
- if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */) lastNonSpaceIndex = i + 1;
- }
+ int lastNonSpaceIndex = 0;
+ boolean justToCursor = false;
+ if (cursorAtThisRow || oldLine.mLineWrap) {
+ // Take the whole line, either because of cursor on it, or if line wrapping.
+ lastNonSpaceIndex = oldLine.getSpaceUsed();
+ if (cursorAtThisRow) justToCursor = true;
+ } else {
+ for (int i = 0; i < oldLine.getSpaceUsed(); i++)
+ // NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
+ if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */)
+ lastNonSpaceIndex = i + 1;
+ }
- int currentOldCol = 0;
- int styleAtCol = 0;
- for (int i = 0; i < lastNonSpaceIndex; i++) {
- // Note that looping over java character, not cells.
- char c = oldLine.mText[i];
- int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c;
- int displayWidth = WcWidth.width(codePoint);
- // Use the last style if this is a zero-width character:
- if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol);
+ int currentOldCol = 0;
+ int styleAtCol = 0;
+ for (int i = 0; i < lastNonSpaceIndex; i++) {
+ // Note that looping over java character, not cells.
+ char c = oldLine.mText[i];
+ int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c;
+ int displayWidth = WcWidth.width(codePoint);
+ // Use the last style if this is a zero-width character:
+ if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol);
- // Line wrap as necessary:
- if (currentOutputExternalColumn + displayWidth > mColumns) {
- setLineWrap(currentOutputExternalRow);
- if (currentOutputExternalRow == mScreenRows - 1) {
- if (newCursorPlaced) newCursorRow--;
- scrollDownOneLine(0, mScreenRows, currentStyle);
- } else {
- currentOutputExternalRow++;
- }
- currentOutputExternalColumn = 0;
- }
+ // Line wrap as necessary:
+ if (currentOutputExternalColumn + displayWidth > mColumns) {
+ setLineWrap(currentOutputExternalRow);
+ if (currentOutputExternalRow == mScreenRows - 1) {
+ if (newCursorPlaced) newCursorRow--;
+ scrollDownOneLine(0, mScreenRows, currentStyle);
+ } else {
+ currentOutputExternalRow++;
+ }
+ currentOutputExternalColumn = 0;
+ }
- int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0);
- int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar;
- setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol);
+ int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0);
+ int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar;
+ setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol);
- if (displayWidth > 0) {
- if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {
- newCursorColumn = currentOutputExternalColumn;
- newCursorRow = currentOutputExternalRow;
- newCursorPlaced = true;
- }
- currentOldCol += displayWidth;
- currentOutputExternalColumn += displayWidth;
- if (justToCursor && newCursorPlaced) break;
- }
- }
- // Old row has been copied. Check if we need to insert newline if old line was not wrapping:
- if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) {
- if (currentOutputExternalRow == mScreenRows - 1) {
- if (newCursorPlaced) newCursorRow--;
- scrollDownOneLine(0, mScreenRows, currentStyle);
- } else {
- currentOutputExternalRow++;
- }
- currentOutputExternalColumn = 0;
- }
- }
+ if (displayWidth > 0) {
+ if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {
+ newCursorColumn = currentOutputExternalColumn;
+ newCursorRow = currentOutputExternalRow;
+ newCursorPlaced = true;
+ }
+ currentOldCol += displayWidth;
+ currentOutputExternalColumn += displayWidth;
+ if (justToCursor && newCursorPlaced) break;
+ }
+ }
+ // Old row has been copied. Check if we need to insert newline if old line was not wrapping:
+ if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) {
+ if (currentOutputExternalRow == mScreenRows - 1) {
+ if (newCursorPlaced) newCursorRow--;
+ scrollDownOneLine(0, mScreenRows, currentStyle);
+ } else {
+ currentOutputExternalRow++;
+ }
+ currentOutputExternalColumn = 0;
+ }
+ }
- cursor[0] = newCursorColumn;
- cursor[1] = newCursorRow;
- }
+ cursor[0] = newCursorColumn;
+ cursor[1] = newCursorRow;
+ }
- // Handle cursor scrolling off screen:
- if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0;
- }
+ // Handle cursor scrolling off screen:
+ if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0;
+ }
- /**
- * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
- * into account.
- *
- * @param srcInternal
- * The first line to be copied.
- * @param len
- * The number of lines to be copied.
- */
- private void blockCopyLinesDown(int srcInternal, int len) {
- if (len == 0) return;
- int totalRows = mTotalRows;
+ /**
+ * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
+ * into account.
+ *
+ * @param srcInternal The first line to be copied.
+ * @param len The number of lines to be copied.
+ */
+ private void blockCopyLinesDown(int srcInternal, int len) {
+ if (len == 0) return;
+ int totalRows = mTotalRows;
- int start = len - 1;
- // Save away line to be overwritten:
- TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows];
- // Do the copy from bottom to top.
- for (int i = start; i >= 0; --i)
- mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows];
- // Put back overwritten line, now above the block:
- mLines[(srcInternal) % totalRows] = lineToBeOverWritten;
- }
+ int start = len - 1;
+ // Save away line to be overwritten:
+ TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows];
+ // Do the copy from bottom to top.
+ for (int i = start; i >= 0; --i)
+ mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows];
+ // Put back overwritten line, now above the block:
+ mLines[(srcInternal) % totalRows] = lineToBeOverWritten;
+ }
- /**
- * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
- *
- * @param topMargin
- * First line that is scrolled.
- * @param bottomMargin
- * One line after the last line that is scrolled.
- * @param style
- * the style for the newly exposed line.
- */
- public void scrollDownOneLine(int topMargin, int bottomMargin, int style) {
- if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
- throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
+ /**
+ * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
+ *
+ * @param topMargin First line that is scrolled.
+ * @param bottomMargin One line after the last line that is scrolled.
+ * @param style the style for the newly exposed line.
+ */
+ public void scrollDownOneLine(int topMargin, int bottomMargin, int style) {
+ if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
+ throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
- // Copy the fixed topMargin lines one line down so that they remain on screen in same position:
- blockCopyLinesDown(mScreenFirstRow, topMargin);
- // Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same
- // position:
- blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin);
+ // Copy the fixed topMargin lines one line down so that they remain on screen in same position:
+ blockCopyLinesDown(mScreenFirstRow, topMargin);
+ // Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same
+ // position:
+ blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin);
- // Update the screen location in the ring buffer:
- mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows;
- // Note that the history has grown if not already full:
- if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++;
+ // Update the screen location in the ring buffer:
+ mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows;
+ // Note that the history has grown if not already full:
+ if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++;
- // Blank the newly revealed line above the bottom margin:
- int blankRow = externalToInternalRow(bottomMargin - 1);
- if (mLines[blankRow] == null) {
- mLines[blankRow] = new TerminalRow(mColumns, style);
- } else {
- mLines[blankRow].clear(style);
- }
- }
+ // Blank the newly revealed line above the bottom margin:
+ int blankRow = externalToInternalRow(bottomMargin - 1);
+ if (mLines[blankRow] == null) {
+ mLines[blankRow] = new TerminalRow(mColumns, style);
+ } else {
+ mLines[blankRow].clear(style);
+ }
+ }
- /**
- * Block copy characters from one position in the screen to another. The two positions can overlap. All characters
- * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
- * be thrown.
- *
- * @param sx
- * source X coordinate
- * @param sy
- * source Y coordinate
- * @param w
- * width
- * @param h
- * height
- * @param dx
- * destination X coordinate
- * @param dy
- * destination Y coordinate
- */
- public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
- if (w == 0) return;
- if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows)
- throw new IllegalArgumentException();
- boolean copyingUp = sy > dy;
- for (int y = 0; y < h; y++) {
- int y2 = copyingUp ? y : (h - (y + 1));
- TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2));
- allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx);
- }
- }
+ /**
+ * Block copy characters from one position in the screen to another. The two positions can overlap. All characters
+ * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
+ * be thrown.
+ *
+ * @param sx source X coordinate
+ * @param sy source Y coordinate
+ * @param w width
+ * @param h height
+ * @param dx destination X coordinate
+ * @param dy destination Y coordinate
+ */
+ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
+ if (w == 0) return;
+ if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows)
+ throw new IllegalArgumentException();
+ boolean copyingUp = sy > dy;
+ for (int y = 0; y < h; y++) {
+ int y2 = copyingUp ? y : (h - (y + 1));
+ TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2));
+ allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx);
+ }
+ }
- /**
- * Block set characters. All characters must be within the bounds of the screen, or else and
- * InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
- * of characters.
- */
- public void blockSet(int sx, int sy, int w, int h, int val, int style) {
- if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
- throw new IllegalArgumentException(
- "Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
- }
- for (int y = 0; y < h; y++)
- for (int x = 0; x < w; x++)
- setChar(sx + x, sy + y, val, style);
- }
+ /**
+ * Block set characters. All characters must be within the bounds of the screen, or else and
+ * InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
+ * of characters.
+ */
+ public void blockSet(int sx, int sy, int w, int h, int val, int style) {
+ if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
+ throw new IllegalArgumentException(
+ "Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
+ }
+ for (int y = 0; y < h; y++)
+ for (int x = 0; x < w; x++)
+ setChar(sx + x, sy + y, val, style);
+ }
- public TerminalRow allocateFullLineIfNecessary(int row) {
- return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
- }
+ public TerminalRow allocateFullLineIfNecessary(int row) {
+ return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
+ }
- public void setChar(int column, int row, int codePoint, int style) {
- if (row >= mScreenRows || column >= mColumns)
- throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
- row = externalToInternalRow(row);
- allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
- }
+ public void setChar(int column, int row, int codePoint, int style) {
+ if (row >= mScreenRows || column >= mColumns)
+ throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
+ row = externalToInternalRow(row);
+ allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
+ }
- public int getStyleAt(int externalRow, int column) {
- return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
- }
+ public int getStyleAt(int externalRow, int column) {
+ return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
+ }
- /** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */
- public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left,
- int bottom, int right) {
- for (int y = top; y < bottom; y++) {
- TerminalRow line = mLines[externalToInternalRow(y)];
- int startOfLine = (rectangular || y == top) ? left : leftMargin;
- int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
- for (int x = startOfLine; x < endOfLine; x++) {
- int currentStyle = line.getStyle(x);
- int foreColor = TextStyle.decodeForeColor(currentStyle);
- int backColor = TextStyle.decodeBackColor(currentStyle);
- int effect = TextStyle.decodeEffect(currentStyle);
- if (reverse) {
- // Clear out the bits to reverse and add them back in reversed:
- effect = (effect & ~bits) | (bits & ~effect);
- } else if (setOrClear) {
- effect |= bits;
- } else {
- effect &= ~bits;
- }
- line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
- }
- }
- }
+ /** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */
+ public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left,
+ int bottom, int right) {
+ for (int y = top; y < bottom; y++) {
+ TerminalRow line = mLines[externalToInternalRow(y)];
+ int startOfLine = (rectangular || y == top) ? left : leftMargin;
+ int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
+ for (int x = startOfLine; x < endOfLine; x++) {
+ int currentStyle = line.getStyle(x);
+ int foreColor = TextStyle.decodeForeColor(currentStyle);
+ int backColor = TextStyle.decodeBackColor(currentStyle);
+ int effect = TextStyle.decodeEffect(currentStyle);
+ if (reverse) {
+ // Clear out the bits to reverse and add them back in reversed:
+ effect = (effect & ~bits) | (bits & ~effect);
+ } else if (setOrClear) {
+ effect |= bits;
+ } else {
+ effect &= ~bits;
+ }
+ line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/termux/terminal/TerminalColorScheme.java b/app/src/main/java/com/termux/terminal/TerminalColorScheme.java
index e330b880..e4fd4e9c 100644
--- a/app/src/main/java/com/termux/terminal/TerminalColorScheme.java
+++ b/app/src/main/java/com/termux/terminal/TerminalColorScheme.java
@@ -6,97 +6,98 @@ import java.util.Properties;
/**
* Color scheme for a terminal with default colors, which may be overridden (and then reset) from the shell using
* Operating System Control (OSC) sequences.
- *
+ *
* @see TerminalColors
*/
public final class TerminalColorScheme {
- /** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */
- private static final int[] DEFAULT_COLORSCHEME = {
- // 16 original colors. First 8 are dim.
- 0xff000000, // black
- 0xffcd0000, // dim red
- 0xff00cd00, // dim green
- 0xffcdcd00, // dim yellow
- 0xff6495ed, // dim blue
- 0xffcd00cd, // dim magenta
- 0xff00cdcd, // dim cyan
- 0xffe5e5e5, // dim white
- // Second 8 are bright:
- 0xff7f7f7f, // medium grey
- 0xffff0000, // bright red
- 0xff00ff00, // bright green
- 0xffffff00, // bright yellow
- 0xff5c5cff, // light blue
- 0xffff00ff, // bright magenta
- 0xff00ffff, // bright cyan
- 0xffffffff, // bright white
+ /** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */
+ private static final int[] DEFAULT_COLORSCHEME = {
+ // 16 original colors. First 8 are dim.
+ 0xff000000, // black
+ 0xffcd0000, // dim red
+ 0xff00cd00, // dim green
+ 0xffcdcd00, // dim yellow
+ 0xff6495ed, // dim blue
+ 0xffcd00cd, // dim magenta
+ 0xff00cdcd, // dim cyan
+ 0xffe5e5e5, // dim white
+ // Second 8 are bright:
+ 0xff7f7f7f, // medium grey
+ 0xffff0000, // bright red
+ 0xff00ff00, // bright green
+ 0xffffff00, // bright yellow
+ 0xff5c5cff, // light blue
+ 0xffff00ff, // bright magenta
+ 0xff00ffff, // bright cyan
+ 0xffffffff, // bright white
- // 216 color cube, six shades of each color:
- 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
- 0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
- 0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
- 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
- 0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
- 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
- 0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
- 0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
- 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
- 0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
- 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
- 0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
- 0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
- 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
- 0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
- 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
- 0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
- 0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
+ // 216 color cube, six shades of each color:
+ 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
+ 0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
+ 0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
+ 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
+ 0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
+ 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
+ 0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
+ 0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
+ 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
+ 0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
+ 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
+ 0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
+ 0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
+ 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
+ 0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
+ 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
+ 0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
+ 0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
- // 24 grey scale ramp:
- 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
- 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
+ // 24 grey scale ramp:
+ 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
+ 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
- // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
- 0xffffffff, 0xff000000, 0xffffffff };
+ // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
+ 0xffffffff, 0xff000000, 0xffffffff};
- public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
+ public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
- public TerminalColorScheme() {
- reset();
- }
+ public TerminalColorScheme() {
+ reset();
+ }
- private void reset() {
- System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
- }
+ private void reset() {
+ System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
+ }
- public void updateWith(Properties props) {
- reset();
- for (Map.Entry