mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-07 03:05:18 +08:00
Various updates mainly for extra keys
This commit is contained in:
@@ -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;
|
||||||
|
mEnabled = enabled;
|
||||||
|
|
||||||
if (enabled == mEnabled) {
|
View decorView = mActivity.getWindow().getDecorView();
|
||||||
if (!enabled) win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mEnabled = enabled;
|
|
||||||
|
|
||||||
final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0);
|
if (enabled) {
|
||||||
if (enabled) {
|
decorView.setOnSystemUiVisibilityChangeListener
|
||||||
win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
(new View.OnSystemUiVisibilityChangeListener() {
|
||||||
setImmersiveMode();
|
@Override
|
||||||
childViewOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
public void onSystemUiVisibilityChange(int visibility) {
|
||||||
} else {
|
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||||
win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
if (mActivity.mSettings.isShowExtraKeys()) {
|
||||||
win.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE);
|
||||||
childViewOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
}
|
||||||
((LayoutParams) childViewOfContent.getLayoutParams()).height = android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
setImmersiveMode();
|
||||||
}
|
} else {
|
||||||
}
|
mActivity.findViewById(R.id.viewpager).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setImmersiveMode();
|
||||||
|
} else {
|
||||||
|
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||||
|
decorView.setOnSystemUiVisibilityChangeListener(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setImmersiveMode() {
|
private static boolean isColorLight(int color) {
|
||||||
mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
|
||||||
}
|
return darkness < 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
void setImmersiveMode() {
|
||||||
public void onGlobalLayout() {
|
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0);
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
if (mEnabled) setImmersiveMode();
|
int color = ((ColorDrawable) mActivity.getWindow().getDecorView().getBackground()).getColor();
|
||||||
|
if (isColorLight(color)) flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||||
childViewOfContent.getWindowVisibleDisplayFrame(mWindowRect);
|
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
|
||||||
int usableHeightNow = Math.min(mWindowRect.height(), childViewOfContent.getRootView().getHeight());
|
}
|
||||||
FrameLayout.LayoutParams layout = (LayoutParams) childViewOfContent.getLayoutParams();
|
|
||||||
if (layout.height != usableHeightNow) {
|
|
||||||
layout.height = usableHeightNow;
|
|
||||||
childViewOfContent.requestLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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,7 +406,12 @@ 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);
|
||||||
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
|
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
|
||||||
@@ -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);
|
||||||
|
283
app/src/main/java/com/termux/app/TermuxKeyListener.java
Normal file
283
app/src/main/java/com/termux/app/TermuxKeyListener.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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++) {
|
||||||
|
@@ -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,28 +245,20 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean commitText(CharSequence text, int newCursorPosition) {
|
public boolean commitText(CharSequence text, int newCursorPosition) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
|
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
final int textLengthInChars = text.length();
|
final int textLengthInChars = text.length();
|
||||||
for (int i = 0; i < textLengthInChars; i++) {
|
for (int i = 0; i < textLengthInChars; i++) {
|
||||||
@@ -296,9 +274,24 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -313,7 +306,18 @@ 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -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,19 +556,22 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
int keyMod = 0;
|
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;
|
||||||
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;
|
||||||
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT;
|
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT;
|
||||||
@@ -608,15 +621,12 @@ 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 (mModifiers.readAltButton()) altDown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int resultingKeyCode = -1; // Set if virtual key causes this to be translated to key event.
|
if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return;
|
||||||
if (controlDown) {
|
|
||||||
|
if (controlDown) {
|
||||||
if (codePoint >= 'a' && codePoint <= 'z') {
|
if (codePoint >= 'a' && codePoint <= 'z') {
|
||||||
codePoint = codePoint - 'a' + 1;
|
codePoint = codePoint - 'a' + 1;
|
||||||
} else if (codePoint >= 'A' && codePoint <= 'Z') {
|
} else if (codePoint >= 'A' && codePoint <= 'Z') {
|
||||||
@@ -635,87 +645,29 @@ 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.
|
if (codePoint > -1) {
|
||||||
case 'p': resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP; break;
|
// Work around bluetooth keyboards sending funny unicode characters instead
|
||||||
case 'n': resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN; break;
|
// of the more normal ones from ASCII that terminal programs expect - the
|
||||||
|
// desire to input the original characters should be low.
|
||||||
// Some special keys:
|
switch (codePoint) {
|
||||||
case 't': resultingKeyCode = KeyEvent.KEYCODE_TAB; break;
|
case 0x02DC: // SMALL TILDE.
|
||||||
case 'i': resultingKeyCode = KeyEvent.KEYCODE_INSERT; break;
|
codePoint = 0x007E; // TILDE (~).
|
||||||
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;
|
break;
|
||||||
case '0':
|
case 0x02CB: // MODIFIER LETTER GRAVE ACCENT.
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
codePoint = 0x0060; // GRAVE ACCENT (`).
|
||||||
break;
|
break;
|
||||||
|
case 0x02C6: // MODIFIER LETTER CIRCUMFLEX ACCENT.
|
||||||
// Other special keys.
|
codePoint = 0x005E; // CIRCUMFLEX ACCENT (^).
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (codePoint > -1) {
|
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
|
||||||
if (resultingKeyCode > -1) {
|
mTermSession.writeCodePoint(altDown, codePoint);
|
||||||
handleKeyCode(resultingKeyCode, 0);
|
}
|
||||||
} else {
|
}
|
||||||
// Work around bluetooth keyboards sending funny unicode characters instead
|
|
||||||
// of the more normal ones from ASCII that terminal programs expect - the
|
|
||||||
// desire to input the original characters should be low.
|
|
||||||
switch (codePoint) {
|
|
||||||
case 0x02DC: // SMALL TILDE.
|
|
||||||
codePoint = 0x007E; // TILDE (~).
|
|
||||||
break;
|
|
||||||
case 0x02CB: // MODIFIER LETTER GRAVE ACCENT.
|
|
||||||
codePoint = 0x0060; // GRAVE ACCENT (`).
|
|
||||||
break;
|
|
||||||
case 0x02C6: // MODIFIER LETTER CIRCUMFLEX ACCENT.
|
|
||||||
codePoint = 0x005E; // CIRCUMFLEX ACCENT (^).
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
|
|
||||||
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.
|
||||||
|
@@ -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"
|
||||||
|
Reference in New Issue
Block a user