Various updates mainly for extra keys

This commit is contained in:
Fredrik Fornwall
2016-06-28 00:56:30 +02:00
parent 964c0b7b4f
commit d72fd579ee
12 changed files with 586 additions and 354 deletions

View File

@@ -1,68 +1,65 @@
package com.termux.app; package com.termux.app;
import android.app.Activity; import android.graphics.Color;
import android.graphics.Rect; import android.graphics.drawable.ColorDrawable;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window; import com.termux.R;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
/** /**
* Utility to make the touch keyboard and immersive mode work with full screen activities. * Utility to manage full screen immersive mode.
* * <p/>
* See https://code.google.com/p/android/issues/detail?id=5497 * See https://code.google.com/p/android/issues/detail?id=5497
*/ */
final class FullScreenHelper implements ViewTreeObserver.OnGlobalLayoutListener { final class FullScreenHelper {
private boolean mEnabled = false; private boolean mEnabled = false;
private final Activity mActivity; final TermuxActivity mActivity;
private final Rect mWindowRect = new Rect();
public FullScreenHelper(Activity activity) { public FullScreenHelper(TermuxActivity activity) {
this.mActivity = activity; this.mActivity = activity;
} }
public void setImmersive(boolean enabled) { public void setImmersive(boolean enabled) {
Window win = mActivity.getWindow(); if (enabled == mEnabled) return;
if (enabled == mEnabled) {
if (!enabled) win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
return;
}
mEnabled = enabled; mEnabled = enabled;
final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0); View decorView = mActivity.getWindow().getDecorView();
if (enabled) { if (enabled) {
win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); decorView.setOnSystemUiVisibilityChangeListener
setImmersiveMode(); (new View.OnSystemUiVisibilityChangeListener() {
childViewOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this);
} else {
win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
win.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
childViewOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
((LayoutParams) childViewOfContent.getLayoutParams()).height = android.view.ViewGroup.LayoutParams.MATCH_PARENT;
}
}
private void setImmersiveMode() {
mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
@Override @Override
public void onGlobalLayout() { public void onSystemUiVisibilityChange(int visibility) {
final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0); if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
if (mActivity.mSettings.isShowExtraKeys()) {
if (mEnabled) setImmersiveMode(); mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE);
}
childViewOfContent.getWindowVisibleDisplayFrame(mWindowRect); setImmersiveMode();
int usableHeightNow = Math.min(mWindowRect.height(), childViewOfContent.getRootView().getHeight()); } else {
FrameLayout.LayoutParams layout = (LayoutParams) childViewOfContent.getLayoutParams(); mActivity.findViewById(R.id.viewpager).setVisibility(View.GONE);
if (layout.height != usableHeightNow) { }
layout.height = usableHeightNow; }
childViewOfContent.requestLayout(); });
setImmersiveMode();
} else {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
decorView.setOnSystemUiVisibilityChangeListener(null);
} }
} }
private static boolean isColorLight(int color) {
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
return darkness < 0.5;
}
void setImmersiveMode() {
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN;
int color = ((ColorDrawable) mActivity.getWindow().getDecorView().getBackground()).getColor();
if (isColorLight(color)) flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
}
} }

View File

@@ -36,6 +36,7 @@ import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity; import android.view.Gravity;
@@ -43,10 +44,8 @@ import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener; import android.view.View.OnLongClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
@@ -61,14 +60,20 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.termux.R; import com.termux.R;
import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.TerminalColors;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSession.SessionChangedCallback; import com.termux.terminal.TerminalSession.SessionChangedCallback;
import com.termux.view.TerminalKeyListener; import com.termux.terminal.TextStyle;
import com.termux.view.TerminalView; import com.termux.view.TerminalView;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -102,6 +107,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
/** The main view of the activity showing the terminal. Initialized in onCreate(). */ /** The main view of the activity showing the terminal. Initialized in onCreate(). */
@SuppressWarnings("NullableProblems") @NonNull TerminalView mTerminalView; @SuppressWarnings("NullableProblems") @NonNull TerminalView mTerminalView;
ExtraKeysView mExtraKeysView;
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this); final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
TermuxPreferences mSettings; TermuxPreferences mSettings;
@@ -139,12 +146,46 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this); if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
return; return;
} }
mTerminalView.checkForFontAndColors(); checkForFontAndColors();
mSettings.reloadFromProperties(TermuxActivity.this); mSettings.reloadFromProperties(TermuxActivity.this);
} }
} }
}; };
void checkForFontAndColors() {
try {
// Hard-coded paths since this file is used also in Termux:Float.
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
final Properties props = new Properties();
if (colorsFile.isFile()) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
TerminalColors.COLOR_SCHEME.updateWith(props);
TerminalSession session = getCurrentTermSession();
if (session != null && session.getEmulator() != null) {
session.getEmulator().mColors.reset();
}
updateBackgroundColor();
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
mTerminalView.setTypeface(newTypeface);
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Error in checkForFontAndColors()", e);
}
}
void updateBackgroundColor() {
TerminalSession session = getCurrentTermSession();
if (session != null && session.getEmulator() != null) {
getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
}
}
/** For processes to access shared internal storage (/sdcard) we need this permission. */ /** For processes to access shared internal storage (/sdcard) we need this permission. */
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
public boolean ensureStoragePermissionGranted() { public boolean ensureStoragePermissionGranted() {
@@ -165,13 +206,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
// Prevent overdraw:
getWindow().getDecorView().setBackground(null);
mSettings = new TermuxPreferences(this); mSettings = new TermuxPreferences(this);
setContentView(R.layout.drawer_layout); 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()); mTerminalView.setTextSize(mSettings.getFontSize());
mFullScreenHelper.setImmersive(mSettings.isFullScreen()); mFullScreenHelper.setImmersive(mSettings.isFullScreen());
@@ -196,10 +235,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this); LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
View layout; View layout;
if (position == 0) { if (position == 0) {
layout = (View) inflater.inflate(R.layout.extra_keys_main, collection, false); layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
mTerminalView.mModifiers = (TerminalView.KeyboardModifiers) layout;
} else { } else {
layout = (View) inflater.inflate(R.layout.extra_keys_right, collection, false); layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
final EditText editText = (EditText) layout.findViewById(R.id.text_input); final EditText editText = (EditText) layout.findViewById(R.id.text_input);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override @Override
@@ -224,7 +262,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
int newHeight;
if (position == 0) { if (position == 0) {
mTerminalView.requestFocus(); mTerminalView.requestFocus();
} else { } else {
@@ -234,120 +271,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
}); });
OnKeyListener keyListener = new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
final TerminalSession currentSession = getCurrentTermSession();
if (currentSession == null) return false;
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
// Return pressed with finished session - remove it.
currentSession.finishIfRunning();
int index = mTermService.removeTermSession(currentSession);
mListViewAdapter.notifyDataSetChanged();
if (mTermService.getSessions().isEmpty()) {
// There are no sessions to show, so finish the activity.
finish();
} else {
if (index >= mTermService.getSessions().size()) {
index = mTermService.getSessions().size() - 1;
}
switchToSession(mTermService.getSessions().get(index));
}
return true;
} else if (!(event.isCtrlPressed() && event.isShiftPressed())) {
// Only hook shortcuts with Ctrl+Shift down.
return false;
}
// Get the unmodified code point:
int unicodeChar = event.getUnicodeChar(0);
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) {
int index = mTermService.getSessions().indexOf(currentSession);
if (++index >= mTermService.getSessions().size()) index = 0;
switchToSession(mTermService.getSessions().get(index));
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP || unicodeChar == 'p' /* previous */) {
int index = mTermService.getSessions().indexOf(currentSession);
if (--index < 0) index = mTermService.getSessions().size() - 1;
switchToSession(mTermService.getSessions().get(index));
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
getDrawer().openDrawer(Gravity.LEFT);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
getDrawer().closeDrawers();
} else if (unicodeChar == 'f'/* full screen */) {
toggleImmersive();
} else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else if (unicodeChar == 'm'/* menu */) {
mTerminalView.showContextMenu();
} else if (unicodeChar == 'r'/* rename */) {
renameSession(currentSession);
} else if (unicodeChar == 'c'/* create */) {
addNewSession(false, null);
} else if (unicodeChar == 'u' /* urls */) {
showUrlSelection();
} else if (unicodeChar == 'v') {
doPaste();
} else if (unicodeChar == '+' || event.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
// We also check for the shifted char here since shift may be required to produce '+',
// see https://github.com/termux/termux-api/issues/2
changeFontSize(true);
} else if (unicodeChar == '-') {
changeFontSize(false);
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
int num = unicodeChar - '1';
if (mTermService.getSessions().size() > num) switchToSession(mTermService.getSessions().get(num));
}
return true;
}
};
mTerminalView.setOnKeyListener(keyListener);
findViewById(R.id.left_drawer_list).setOnKeyListener(keyListener);
mTerminalView.setOnKeyListener(new TerminalKeyListener() {
@Override
public float onScale(float scale) {
if (scale < 0.9f || scale > 1.1f) {
boolean increase = scale > 1.f;
changeFontSize(increase);
return 1.0f;
}
return scale;
}
@Override
public void onSingleTapUp(MotionEvent e) {
InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.showSoftInput(mTerminalView, InputMethodManager.SHOW_IMPLICIT);
}
@Override
public boolean shouldBackButtonBeMappedToEscape() {
return mSettings.mBackIsEscape;
}
@Override
public void copyModeChanged(boolean copyMode) {
// Disable drawer while copying.
getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
}
});
View newSessionButton = findViewById(R.id.new_session_button); View newSessionButton = findViewById(R.id.new_session_button);
newSessionButton.setOnClickListener(new OnClickListener() { newSessionButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
addNewSession(false, null); addNewSession(false, null);
} }
}); });
newSessionButton.setOnLongClickListener(new OnLongClickListener() { newSessionButton.setOnLongClickListener(new OnLongClickListener() {
@Override @Override
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
@@ -380,9 +310,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() { findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() {
@Override @Override
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
View extraKeysView = findViewById(R.id.viewpager); toggleShowExtraKeys();
mSettings.toggleShowExtraKeys(TermuxActivity.this);
extraKeysView.setVisibility(mSettings.isShowExtraKeys() ? View.VISIBLE : View.GONE);
return true; return true;
} }
}); });
@@ -394,11 +322,21 @@ public final class TermuxActivity extends Activity implements ServiceConnection
startService(serviceIntent); startService(serviceIntent);
if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed"); if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed");
mTerminalView.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);
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
if (showNow && viewPager.getCurrentItem() == 1) {
// Focus the text input view if just revealed.
findViewById(R.id.text_input).requestFocus();
}
}
/** /**
* Part of the {@link ServiceConnection} interface. The service is bound with * 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 * {@link #bindService(Intent, ServiceConnection, int)} in {@link #onCreate(Bundle)} which will cause a call to this
@@ -468,6 +406,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
} }
@Override
public void onColorsChanged(TerminalSession changedSession) {
if (getCurrentTermSession() == changedSession) updateBackgroundColor();
}
}; };
ListView listView = (ListView) findViewById(R.id.left_drawer_list); ListView listView = (ListView) findViewById(R.id.left_drawer_list);
@@ -563,6 +506,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
} }
public void switchToSession(boolean forward) {
TerminalSession currentSession = getCurrentTermSession();
int index = mTermService.getSessions().indexOf(currentSession);
if (forward) {
if (++index >= mTermService.getSessions().size()) index = 0;
} else {
if (--index < 0) index = mTermService.getSessions().size() - 1;
}
switchToSession(mTermService.getSessions().get(index));
}
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
void renameSession(final TerminalSession sessionToRename) { void renameSession(final TerminalSession sessionToRename) {
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() { DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() {
@@ -654,7 +608,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
/** Try switching to session and note about it, but do nothing if already displaying the session. */ /** Try switching to session and note about it, but do nothing if already displaying the session. */
void switchToSession(TerminalSession session) { void switchToSession(TerminalSession session) {
if (mTerminalView.attachSession(session)) noteSessionInfo(); if (mTerminalView.attachSession(session)) {
noteSessionInfo();
updateBackgroundColor();
}
} }
String toToastTitle(TerminalSession session) { String toToastTitle(TerminalSession session) {
@@ -691,7 +648,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url); 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_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_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, R.string.kill_process).setEnabled(currentSession.isRunning()); 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_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_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_HELP_ID, Menu.NONE, R.string.help);

View File

@@ -0,0 +1,283 @@
package com.termux.app;
import android.content.Context;
import android.media.AudioManager;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import com.termux.view.TerminalKeyListener;
import java.util.List;
public final class TermuxKeyListener implements TerminalKeyListener {
final TermuxActivity mActivity;
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
public TermuxKeyListener(TermuxActivity activity) {
this.mActivity = activity;
}
@Override
public float onScale(float scale) {
if (scale < 0.9f || scale > 1.1f) {
boolean increase = scale > 1.f;
mActivity.changeFontSize(increase);
return 1.0f;
}
return scale;
}
@Override
public void onSingleTapUp(MotionEvent e) {
InputMethodManager mgr = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.showSoftInput(mActivity.mTerminalView, InputMethodManager.SHOW_IMPLICIT);
}
@Override
public boolean shouldBackButtonBeMappedToEscape() {
return mActivity.mSettings.mBackIsEscape;
}
@Override
public void copyModeChanged(boolean copyMode) {
// Disable drawer while copying.
mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) {
if (handleVirtualKeys(keyCode, e, true)) return true;
TermuxService service = mActivity.mTermService;
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
// Return pressed with finished session - remove it.
currentSession.finishIfRunning();
int index = service.removeTermSession(currentSession);
mActivity.mListViewAdapter.notifyDataSetChanged();
if (mActivity.mTermService.getSessions().isEmpty()) {
// There are no sessions to show, so finish the activity.
mActivity.finish();
} else {
if (index >= service.getSessions().size()) {
index = service.getSessions().size() - 1;
}
mActivity.switchToSession(service.getSessions().get(index));
}
return true;
} else if (e.isCtrlPressed() && e.isShiftPressed()) {
// Get the unmodified code point:
int unicodeChar = e.getUnicodeChar(0);
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) {
mActivity.switchToSession(true);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP || unicodeChar == 'p' /* previous */) {
mActivity.switchToSession(false);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
mActivity.getDrawer().openDrawer(Gravity.LEFT);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
mActivity.getDrawer().closeDrawers();
} else if (unicodeChar == 'f'/* full screen */) {
mActivity.toggleImmersive();
} else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else if (unicodeChar == 'm'/* menu */) {
mActivity.mTerminalView.showContextMenu();
} else if (unicodeChar == 'r'/* rename */) {
mActivity.renameSession(currentSession);
} else if (unicodeChar == 'c'/* create */) {
mActivity.addNewSession(false, null);
} else if (unicodeChar == 'u' /* urls */) {
mActivity.showUrlSelection();
} else if (unicodeChar == 'v') {
mActivity.doPaste();
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
// We also check for the shifted char here since shift may be required to produce '+',
// see https://github.com/termux/termux-api/issues/2
mActivity.changeFontSize(true);
} else if (unicodeChar == '-') {
mActivity.changeFontSize(false);
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
int num = unicodeChar - '1';
if (service.getSessions().size() > num) mActivity.switchToSession(service.getSessions().get(num));
}
return true;
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent e) {
return handleVirtualKeys(keyCode, e, false);
}
@Override
public boolean readControlKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readControlButton()) || mVirtualControlKeyDown;
}
@Override
public boolean readAltKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readAltButton());
}
@Override
public boolean onCodePoint(final int codePoint, boolean ctrlDown, TerminalSession session) {
if (mVirtualFnKeyDown) {
int resultingKeyCode = -1;
int resultingCodePoint = -1;
boolean altDown = false;
int lowerCase = Character.toLowerCase(codePoint);
switch (lowerCase) {
// Arrow keys.
case 'w':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
break;
case 'a':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
break;
case 's':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
break;
case 'd':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
break;
// Page up and down.
case 'p':
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
break;
case 'n':
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
break;
// Some special keys:
case 't':
resultingKeyCode = KeyEvent.KEYCODE_TAB;
break;
case 'i':
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
break;
case 'h':
resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME;
break;
// Special characters to input.
case 'u':
resultingCodePoint = '_';
break;
case 'l':
resultingCodePoint = '|';
break;
// Function keys.
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
break;
case '0':
resultingKeyCode = KeyEvent.KEYCODE_F10;
break;
// Other special keys.
case 'e':
resultingCodePoint = /*Escape*/ 27;
break;
case '.':
resultingCodePoint = /*^.*/ 28;
break;
case 'b': // alt+b, jumping backward in readline.
case 'f': // alf+f, jumping forward in readline.
case 'x': // alt+x, common in emacs.
resultingCodePoint = lowerCase;
altDown = true;
break;
// Volume control.
case 'v':
resultingCodePoint = -1;
AudioManager audio = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
break;
// Writing mode:
case 'q':
mActivity.toggleShowExtraKeys();
break;
}
if (resultingKeyCode != -1) {
TerminalEmulator term = session.getEmulator();
session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()));
} else if (resultingCodePoint != -1) {
session.writeCodePoint(altDown, resultingCodePoint);
}
return true;
} else if (ctrlDown) {
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
if (!shortcuts.isEmpty()) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
TermuxPreferences.KeyboardShortcut shortcut = shortcuts.get(i);
if (codePoint == shortcut.codePoint) {
switch (shortcut.shortcutAction) {
case TermuxPreferences.SHORTCUT_ACTION_CREATE_SESSION:
mActivity.addNewSession(false, null);
return true;
case TermuxPreferences.SHORTCUT_ACTION_PREVIOUS_SESSION:
mActivity.switchToSession(false);
return true;
case TermuxPreferences.SHORTCUT_ACTION_NEXT_SESSION:
mActivity.switchToSession(true);
return true;
case TermuxPreferences.SHORTCUT_ACTION_RENAME_SESSION:
mActivity.renameSession(mActivity.getCurrentTermSession());
return true;
}
}
}
}
}
return false;
}
/** Handle dedicated volume buttons as virtual keys if applicable. */
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
InputDevice inputDevice = event.getDevice();
if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
// Do not steal dedicated buttons from a full external keyboard.
return false;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
mVirtualControlKeyDown = down;
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mVirtualFnKeyDown = down;
return true;
}
return false;
}
}

View File

@@ -14,6 +14,8 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties; import java.util.Properties;
final class TermuxPreferences { final class TermuxPreferences {
@@ -83,9 +85,10 @@ final class TermuxPreferences {
return mShowExtraKeys; return mShowExtraKeys;
} }
void toggleShowExtraKeys(Context context) { boolean toggleShowExtraKeys(Context context) {
mShowExtraKeys = !mShowExtraKeys; mShowExtraKeys = !mShowExtraKeys;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_EXTRA_KEYS_KEY, mShowExtraKeys).apply(); PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_EXTRA_KEYS_KEY, mShowExtraKeys).apply();
return mShowExtraKeys;
} }
int getFontSize() { int getFontSize() {
@@ -123,7 +126,9 @@ final class TermuxPreferences {
public void reloadFromProperties(Context context) { public void reloadFromProperties(Context context) {
try { try {
File propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties"); 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(); Properties props = new Properties();
if (propsFile.isFile() && propsFile.canRead()) { if (propsFile.isFile() && propsFile.canRead()) {
try (FileInputStream in = new FileInputStream(propsFile)) { try (FileInputStream in = new FileInputStream(propsFile)) {
@@ -144,10 +149,57 @@ final class TermuxPreferences {
} }
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) { } catch (Exception e) {
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
Log.e("termux", "Error loading props", e); 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;
public static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
public static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
public final static class KeyboardShortcut {
public KeyboardShortcut(int codePoint, int shortcutAction) {
this.codePoint = codePoint;
this.shortcutAction = shortcutAction;
}
final int codePoint;
final int shortcutAction;
}
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
private void parseAction(String name, int shortcutAction, Properties props) {
String value = props.getProperty(name);
if (value == null) return;
String[] parts = value.trim().split("\\+");
String input = parts.length == 2 ? parts[1].trim() : null;
if (!(parts.length == 2 && parts[0].trim().equalsIgnoreCase("ctrl")) || input.isEmpty() || input.length() > 2) {
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
return;
}
char c = input.charAt(0);
int codePoint = c;
if (Character.isLowSurrogate(c)) {
if (input.length() != 2 || Character.isHighSurrogate(input.charAt(1))) {
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
return;
} else {
codePoint = Character.toCodePoint(input.charAt(1), c);
}
}
shortcuts.add(new KeyboardShortcut(codePoint, shortcutAction));
}
} }

View File

@@ -352,4 +352,9 @@ public final class TermuxService extends Service implements SessionChangedCallba
if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session); if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
} }
@Override
public void onColorsChanged(TerminalSession session) {
if (mSessionChangeCallback != null) mSessionChangeCallback.onColorsChanged(session);
}
} }

View File

@@ -1816,6 +1816,7 @@ public final class TerminalEmulator {
return; return;
} else { } else {
mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i)); mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i));
mSession.onColorsChanged();
colorIndex = -1; colorIndex = -1;
parsingPairStart = -1; parsingPairStart = -1;
} }
@@ -1851,6 +1852,7 @@ public final class TerminalEmulator {
+ String.format(Locale.US, "%04x", b) + bellOrStringTerminator); + String.format(Locale.US, "%04x", b) + bellOrStringTerminator);
} else { } else {
mColors.tryParseColor(specialIndex, colorSpec); mColors.tryParseColor(specialIndex, colorSpec);
mSession.onColorsChanged();
} }
specialIndex++; specialIndex++;
if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length()) break; if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length()) break;
@@ -1877,6 +1879,7 @@ public final class TerminalEmulator {
// parameters are given, the entire table will be reset. // parameters are given, the entire table will be reset.
if (textParameter.isEmpty()) { if (textParameter.isEmpty()) {
mColors.reset(); mColors.reset();
mSession.onColorsChanged();
} else { } else {
int lastIndex = 0; int lastIndex = 0;
for (int charIndex = 0;; charIndex++) { for (int charIndex = 0;; charIndex++) {
@@ -1885,6 +1888,7 @@ public final class TerminalEmulator {
try { try {
int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex)); int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex));
mColors.reset(colorToReset); mColors.reset(colorToReset);
mSession.onColorsChanged();
if (endOfInput) break; if (endOfInput) break;
charIndex++; charIndex++;
lastIndex = charIndex; lastIndex = charIndex;
@@ -1899,6 +1903,7 @@ public final class TerminalEmulator {
case 111: // Reset background color. case 111: // Reset background color.
case 112: // Reset cursor color. case 112: // Reset cursor color.
mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110)); mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110));
mSession.onColorsChanged();
break; break;
case 119: // Reset highlight color. case 119: // Reset highlight color.
break; break;
@@ -2273,6 +2278,7 @@ public final class TerminalEmulator {
mUtf8Index = mUtf8ToFollow = 0; mUtf8Index = mUtf8ToFollow = 0;
mColors.reset(); mColors.reset();
mSession.onColorsChanged();
} }
public String getSelectedText(int x1, int y1, int x2, int y2) { public String getSelectedText(int x1, int y1, int x2, int y2) {

View File

@@ -23,4 +23,6 @@ public abstract class TerminalOutput {
/** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */ /** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */
public abstract void onBell(); public abstract void onBell();
public abstract void onColorsChanged();
} }

View File

@@ -41,6 +41,9 @@ public final class TerminalSession extends TerminalOutput {
void onClipboardText(TerminalSession session, String text); void onClipboardText(TerminalSession session, String text);
void onBell(TerminalSession session); void onBell(TerminalSession session);
void onColorsChanged(TerminalSession session);
} }
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) { private static FileDescriptor wrapFileDescriptor(int fileDescriptor) {
@@ -329,4 +332,11 @@ public final class TerminalSession extends TerminalOutput {
mChangeCallback.onBell(this); mChangeCallback.onBell(this);
} }
@Override
public void onColorsChanged() {
mChangeCallback.onColorsChanged(this);
}
public int getPid() { return mShellPid; }
} }

View File

@@ -1,8 +1,11 @@
package com.termux.view; package com.termux.view;
import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
import com.termux.terminal.TerminalSession;
/** /**
* Input and scale listener which may be set on a {@link TerminalView} through * Input and scale listener which may be set on a {@link TerminalView} through
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}. * {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
@@ -21,4 +24,14 @@ public interface TerminalKeyListener {
void copyModeChanged(boolean copyMode); void copyModeChanged(boolean copyMode);
boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session);
boolean onKeyUp(int keyCode, KeyEvent e);
boolean readControlKey();
boolean readAltKey();
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
} }

View File

@@ -64,8 +64,7 @@ final class TerminalRenderer {
final TerminalBuffer screen = mEmulator.getScreen(); final TerminalBuffer screen = mEmulator.getScreen();
final int[] palette = mEmulator.mColors.mCurrentColors; final int[] palette = mEmulator.mColors.mCurrentColors;
int fillColor = palette[reverseVideo ? TextStyle.COLOR_INDEX_FOREGROUND : TextStyle.COLOR_INDEX_BACKGROUND]; if (reverseVideo) canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC);
canvas.drawColor(fillColor, PorterDuff.Mode.SRC);
float heightOffset = mFontLineSpacingAndAscent; float heightOffset = mFontLineSpacingAndAscent;
for (int row = topRow; row < endRow; row++) { for (int row = topRow; row < endRow; row++) {

View File

@@ -10,7 +10,6 @@ import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
@@ -34,28 +33,15 @@ import com.termux.R;
import com.termux.terminal.EmulatorDebug; import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.KeyHandler; import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalBuffer; import com.termux.terminal.TerminalBuffer;
import com.termux.terminal.TerminalColors;
import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
/** View displaying and interacting with a {@link TerminalSession}. */ /** View displaying and interacting with a {@link TerminalSession}. */
public final class TerminalView extends View { public final class TerminalView extends View {
/** Log view key and IME events. */ /** Log view key and IME events. */
private static final boolean LOG_KEY_EVENTS = false; private static final boolean LOG_KEY_EVENTS = false;
public interface KeyboardModifiers {
boolean readControlButton();
boolean readAltButton();
}
public KeyboardModifiers mModifiers;
/** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */ /** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */
TerminalSession mTermSession; TerminalSession mTermSession;
/** Our terminal emulator whose session is {@link #mTermSession}. */ /** Our terminal emulator whose session is {@link #mTermSession}. */
@@ -68,9 +54,6 @@ public final class TerminalView extends View {
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */ /** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
int mTopRow; int mTopRow;
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection; boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection;
int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1; int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
float mSelectionDownX, mSelectionDownY; float mSelectionDownX, mSelectionDownY;
@@ -245,10 +228,13 @@ public final class TerminalView extends View {
// //
// If using just "TYPE_NULL", there is a problem with the "Google Pinyin Input" being in // If using just "TYPE_NULL", there is a problem with the "Google Pinyin Input" being in
// word mode when used with the "En" tab available when the "Show English keyboard" option // word mode when used with the "En" tab available when the "Show English keyboard" option
// is enabled - see https://github.com/termux/termux-packages/issues/25. // is enabled - see https://github.com/termux/termux-packages/issues/25. It also causes
// the normal Google keyboard to show a row of numbers, see
// https://github.com/termux/termux-app/issues/87
// //
// Adding TYPE_TEXT_FLAG_NO_SUGGESTIONS fixes Pinyin Input, put causes Swype to be put in // Adding TYPE_TEXT_FLAG_NO_SUGGESTIONS fixes Pinyin Input and removes the row of numbers
// word mode... Using TYPE_TEXT_VARIATION_VISIBLE_PASSWORD fixes that. // on the Google keyboard. . It also causes Swype to be put in
// word mode, but using TYPE_TEXT_VARIATION_VISIBLE_PASSWORD fixes that.
// //
// So a bit messy. If this gets too messy it's perhaps best resolved by reverting back to just // So a bit messy. If this gets too messy it's perhaps best resolved by reverting back to just
// "TYPE_NULL" and let the Pinyin Input english keyboard be in word mode. // "TYPE_NULL" and let the Pinyin Input english keyboard be in word mode.
@@ -259,22 +245,14 @@ public final class TerminalView extends View {
return new BaseInputConnection(this, true) { return new BaseInputConnection(this, true) {
@Override
public boolean beginBatchEdit() {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: beginBatchEdit()");
return true;
}
@Override
public boolean endBatchEdit() {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: endBatchEdit()");
return false;
}
@Override @Override
public boolean finishComposingText() { public boolean finishComposingText() {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()"); if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()");
commitText(getEditable(), 0); commitText(getEditable(), 0);
// Clear the editable.
getEditable().clear();
return true; return true;
} }
@@ -296,8 +274,23 @@ public final class TerminalView extends View {
} else { } else {
codePoint = firstChar; codePoint = firstChar;
} }
inputCodePoint(codePoint, false, false);
boolean ctrlHeld = false;
if (codePoint <= 31 && codePoint != 27) {
// E.g. penti keyboard for ctrl input.
ctrlHeld = true;
switch (codePoint) {
case 31: codePoint = '_'; break;
case 30: codePoint = '^'; break;
case 29: codePoint = ']'; break;
case 28: codePoint = '\\'; break;
default: codePoint += 96; break;
} }
}
inputCodePoint(codePoint, ctrlHeld, false);
}
return true; return true;
} }
@@ -313,6 +306,17 @@ public final class TerminalView extends View {
return true; return true;
} }
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
if (text.length() == 0) {
// Avoid log spam "SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot
// have a zero length" when backspacing with the Google keyboard.
getEditable().clear();
} else {
super.setComposingText(text, newCursorPosition);
}
return true;
}
}; };
} }
@@ -378,6 +382,12 @@ public final class TerminalView extends View {
updateSize(); updateSize();
} }
public void setTypeface(Typeface newTypeface) {
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
updateSize();
invalidate();
}
@Override @Override
public boolean onCheckIsTextEditor() { public boolean onCheckIsTextEditor() {
return true; return true;
@@ -546,18 +556,21 @@ public final class TerminalView extends View {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")"); if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
if (mEmulator == null) return true; if (mEmulator == null) return true;
int metaState = event.getMetaState(); if (mOnKeyListener.onKeyDown(keyCode, event, mTermSession)) {
boolean controlDownFromEvent = event.isCtrlPressed();
boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
if (handleVirtualKeys(keyCode, event, true)) {
invalidate(); invalidate();
return true; return true;
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) { } else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
mTermSession.write(event.getCharacters());
return true;
} }
final int metaState = event.getMetaState();
final boolean controlDownFromEvent = event.isCtrlPressed();
final boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
int keyMod = 0; int keyMod = 0;
if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL; if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL;
if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT; if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT;
@@ -608,14 +621,11 @@ public final class TerminalView extends View {
+ leftAltDownFromEvent + ")"); + leftAltDownFromEvent + ")");
} }
boolean controlDown = controlDownFromEvent || mVirtualControlKeyDown; final boolean controlDown = controlDownFromEvent || mOnKeyListener.readControlKey();
boolean altDown = leftAltDownFromEvent; final boolean altDown = leftAltDownFromEvent || mOnKeyListener.readAltKey();
if (mModifiers != null) {
if (mModifiers.readControlButton()) controlDown = true; if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return;
if (mModifiers.readAltButton()) altDown = true;
}
int resultingKeyCode = -1; // Set if virtual key causes this to be translated to key event.
if (controlDown) { if (controlDown) {
if (codePoint >= 'a' && codePoint <= 'z') { if (codePoint >= 'a' && codePoint <= 'z') {
codePoint = codePoint - 'a' + 1; codePoint = codePoint - 'a' + 1;
@@ -635,67 +645,10 @@ public final class TerminalView extends View {
codePoint = 31; codePoint = 31;
} else if (codePoint == '8') { } else if (codePoint == '8') {
codePoint = 127; // DEL codePoint = 127; // DEL
} else if (codePoint == '9') {
resultingKeyCode = KeyEvent.KEYCODE_F11;
} else if (codePoint == '0') {
resultingKeyCode = KeyEvent.KEYCODE_F12;
}
} else if (mVirtualFnKeyDown) {
int lowerCase = Character.toLowerCase(codePoint);
switch (lowerCase) {
// Arrow keys.
case 'w': resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP; break;
case 'a': resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT; break;
case 's': resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN; break;
case 'd': resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT; break;
// Page up and down.
case 'p': resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP; break;
case 'n': resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN; break;
// Some special keys:
case 't': resultingKeyCode = KeyEvent.KEYCODE_TAB; break;
case 'i': resultingKeyCode = KeyEvent.KEYCODE_INSERT; break;
case 'h': resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME; break;
// Special characters to input.
case 'u': codePoint = '_'; break;
case 'l': codePoint = '|'; break;
// Function keys.
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
break;
case '0':
resultingKeyCode = KeyEvent.KEYCODE_F10;
break;
// Other special keys.
case 'e': codePoint = /*Escape*/ 27; break;
case '.': codePoint = /*^.*/ 28; break;
case 'b': // alt+b, jumping backward in readline.
case 'f': // alf+f, jumping forward in readline.
case 'x': // alt+x, common in emacs.
codePoint = lowerCase;
altDown = true;
break;
// Volume control.
case 'v':
codePoint = -1;
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
break;
} }
} }
if (codePoint > -1) { if (codePoint > -1) {
if (resultingKeyCode > -1) {
handleKeyCode(resultingKeyCode, 0);
} else {
// Work around bluetooth keyboards sending funny unicode characters instead // Work around bluetooth keyboards sending funny unicode characters instead
// of the more normal ones from ASCII that terminal programs expect - the // of the more normal ones from ASCII that terminal programs expect - the
// desire to input the original characters should be low. // desire to input the original characters should be low.
@@ -715,7 +668,6 @@ public final class TerminalView extends View {
mTermSession.writeCodePoint(altDown, codePoint); mTermSession.writeCodePoint(altDown, codePoint);
} }
} }
}
/** Input the specified keyCode if applicable and return if the input was consumed. */ /** Input the specified keyCode if applicable and return if the input was consumed. */
public boolean handleKeyCode(int keyCode, int keyMod) { public boolean handleKeyCode(int keyCode, int keyMod) {
@@ -740,7 +692,7 @@ public final class TerminalView extends View {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
if (mEmulator == null) return true; if (mEmulator == null) return true;
if (handleVirtualKeys(keyCode, event, false)) { if (mOnKeyListener.onKeyUp(keyCode, event)) {
invalidate(); invalidate();
return true; return true;
} else if (event.isSystem()) { } else if (event.isSystem()) {
@@ -751,49 +703,6 @@ public final class TerminalView extends View {
return true; return true;
} }
/** Handle dedicated volume buttons as virtual keys if applicable. */
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
InputDevice inputDevice = event.getDevice();
if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
// Do not steal dedicated buttons from a full external keyboard.
return false;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleVirtualKeys(down=" + down + ") taking ctrl event");
mVirtualControlKeyDown = down;
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleVirtualKeys(down=" + down + ") taking Fn event");
mVirtualFnKeyDown = down;
return true;
}
return false;
}
public void checkForFontAndColors() {
try {
// Hard-coded paths since this file is used also in Termux:Float.
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
final Properties props = new Properties();
if (colorsFile.isFile()) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
TerminalColors.COLOR_SCHEME.updateWith(props);
if (mEmulator != null) mEmulator.mColors.reset();
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
updateSize();
invalidate();
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Error in checkForFontAndColors()", e);
}
}
/** /**
* This is called during layout when the size of this view has changed. If you were just added to the view * This is called during layout when the size of this view has changed. If you were just added to the view
* hierarchy, you're called with the old values of 0. * hierarchy, you're called with the old values of 0.

View File

@@ -9,7 +9,6 @@
android:inputType="text" android:inputType="text"
android:singleLine="true" android:singleLine="true"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:backgroundTint="@android:color/transparent"
android:paddingTop="0dp" android:paddingTop="0dp"
android:textCursorDrawable="@null" android:textCursorDrawable="@null"
android:paddingBottom="0dp" android:paddingBottom="0dp"