From d72fd579eea1c7d0bdcab0625d214b0a58d6d1c5 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Tue, 28 Jun 2016 00:56:30 +0200 Subject: [PATCH] Various updates mainly for extra keys --- .../java/com/termux/app/FullScreenHelper.java | 101 +++---- .../java/com/termux/app/TermuxActivity.java | 207 +++++-------- .../com/termux/app/TermuxKeyListener.java | 283 ++++++++++++++++++ .../com/termux/app/TermuxPreferences.java | 56 +++- .../java/com/termux/app/TermuxService.java | 5 + .../com/termux/terminal/TerminalEmulator.java | 6 + .../com/termux/terminal/TerminalOutput.java | 2 + .../com/termux/terminal/TerminalSession.java | 10 + .../com/termux/view/TerminalKeyListener.java | 13 + .../com/termux/view/TerminalRenderer.java | 3 +- .../java/com/termux/view/TerminalView.java | 253 +++++----------- app/src/main/res/layout/extra_keys_right.xml | 1 - 12 files changed, 586 insertions(+), 354 deletions(-) create mode 100644 app/src/main/java/com/termux/app/TermuxKeyListener.java diff --git a/app/src/main/java/com/termux/app/FullScreenHelper.java b/app/src/main/java/com/termux/app/FullScreenHelper.java index 08164cce..4c284dd6 100644 --- a/app/src/main/java/com/termux/app/FullScreenHelper.java +++ b/app/src/main/java/com/termux/app/FullScreenHelper.java @@ -1,68 +1,65 @@ package com.termux.app; -import android.app.Activity; -import android.graphics.Rect; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.view.View; -import android.view.ViewTreeObserver; -import android.view.Window; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; + +import com.termux.R; /** - * Utility to make the touch keyboard and immersive mode work with full screen activities. - * + * Utility to manage full screen immersive mode. + *

* 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 final Activity mActivity; - private final Rect mWindowRect = new Rect(); + private boolean mEnabled = false; + final TermuxActivity mActivity; - public FullScreenHelper(Activity activity) { - this.mActivity = activity; - } + public FullScreenHelper(TermuxActivity activity) { + this.mActivity = activity; + } - public void setImmersive(boolean enabled) { - Window win = mActivity.getWindow(); + public void setImmersive(boolean enabled) { + if (enabled == mEnabled) return; + mEnabled = enabled; - if (enabled == mEnabled) { - if (!enabled) win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN); - return; - } - mEnabled = enabled; + View decorView = mActivity.getWindow().getDecorView(); - final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0); - if (enabled) { - win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - setImmersiveMode(); - 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; - } - } + if (enabled) { + decorView.setOnSystemUiVisibilityChangeListener + (new View.OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int visibility) { + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + if (mActivity.mSettings.isShowExtraKeys()) { + mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE); + } + 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() { - mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } + 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; + } - @Override - public void onGlobalLayout() { - final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0); - - if (mEnabled) setImmersiveMode(); - - childViewOfContent.getWindowVisibleDisplayFrame(mWindowRect); - int usableHeightNow = Math.min(mWindowRect.height(), childViewOfContent.getRootView().getHeight()); - FrameLayout.LayoutParams layout = (LayoutParams) childViewOfContent.getLayoutParams(); - if (layout.height != usableHeightNow) { - layout.height = usableHeightNow; - childViewOfContent.requestLayout(); - } - } + 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); + } } diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 212d39d5..b66e2f62 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -36,6 +36,7 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.style.StyleSpan; +import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -43,10 +44,8 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; -import android.view.View.OnKeyListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.WindowManager; @@ -61,14 +60,20 @@ import android.widget.TextView; import android.widget.Toast; import com.termux.R; +import com.termux.terminal.EmulatorDebug; +import com.termux.terminal.TerminalColors; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession.SessionChangedCallback; -import com.termux.view.TerminalKeyListener; +import com.termux.terminal.TextStyle; import com.termux.view.TerminalView; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.Properties; import java.util.regex.Matcher; 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(). */ @SuppressWarnings("NullableProblems") @NonNull TerminalView mTerminalView; + ExtraKeysView mExtraKeysView; + final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this); TermuxPreferences mSettings; @@ -139,12 +146,46 @@ public final class TermuxActivity extends Activity implements ServiceConnection if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this); return; } - mTerminalView.checkForFontAndColors(); + checkForFontAndColors(); 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. */ @TargetApi(Build.VERSION_CODES.M) public boolean ensureStoragePermissionGranted() { @@ -165,13 +206,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection public void onCreate(Bundle bundle) { super.onCreate(bundle); - // Prevent overdraw: - getWindow().getDecorView().setBackground(null); - mSettings = new TermuxPreferences(this); setContentView(R.layout.drawer_layout); mTerminalView = (TerminalView) findViewById(R.id.terminal_view); + mTerminalView.setOnKeyListener(new TermuxKeyListener(this)); mTerminalView.setTextSize(mSettings.getFontSize()); mFullScreenHelper.setImmersive(mSettings.isFullScreen()); @@ -196,10 +235,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this); View layout; if (position == 0) { - layout = (View) inflater.inflate(R.layout.extra_keys_main, collection, false); - mTerminalView.mModifiers = (TerminalView.KeyboardModifiers) layout; + layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false); } 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); editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override @@ -224,7 +262,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { - int newHeight; if (position == 0) { mTerminalView.requestFocus(); } 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); - newSessionButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { addNewSession(false, null); } }); - newSessionButton.setOnLongClickListener(new OnLongClickListener() { @Override 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() { @Override public boolean onLongClick(View v) { - View extraKeysView = findViewById(R.id.viewpager); - mSettings.toggleShowExtraKeys(TermuxActivity.this); - extraKeysView.setVisibility(mSettings.isShowExtraKeys() ? View.VISIBLE : View.GONE); + toggleShowExtraKeys(); return true; } }); @@ -394,11 +322,21 @@ public final class TermuxActivity extends Activity implements ServiceConnection startService(serviceIntent); if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed"); - mTerminalView.checkForFontAndColors(); + checkForFontAndColors(); 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 * {@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); mListViewAdapter = new ArrayAdapter(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") void renameSession(final TerminalSession sessionToRename) { 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. */ void switchToSession(TerminalSession session) { - if (mTerminalView.attachSession(session)) noteSessionInfo(); + if (mTerminalView.attachSession(session)) { + noteSessionInfo(); + updateBackgroundColor(); + } } 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_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share); menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal); - menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, 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_STYLING_ID, Menu.NONE, R.string.style_terminal); menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help); diff --git a/app/src/main/java/com/termux/app/TermuxKeyListener.java b/app/src/main/java/com/termux/app/TermuxKeyListener.java new file mode 100644 index 00000000..df9d1207 --- /dev/null +++ b/app/src/main/java/com/termux/app/TermuxKeyListener.java @@ -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 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; + } + + +} diff --git a/app/src/main/java/com/termux/app/TermuxPreferences.java b/app/src/main/java/com/termux/app/TermuxPreferences.java index 54b04d0c..a4ef7798 100644 --- a/app/src/main/java/com/termux/app/TermuxPreferences.java +++ b/app/src/main/java/com/termux/app/TermuxPreferences.java @@ -14,6 +14,8 @@ import java.io.File; import java.io.FileInputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; final class TermuxPreferences { @@ -83,9 +85,10 @@ final class TermuxPreferences { return mShowExtraKeys; } - void toggleShowExtraKeys(Context context) { + boolean toggleShowExtraKeys(Context context) { mShowExtraKeys = !mShowExtraKeys; PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_EXTRA_KEYS_KEY, mShowExtraKeys).apply(); + return mShowExtraKeys; } int getFontSize() { @@ -123,7 +126,9 @@ final class TermuxPreferences { public void reloadFromProperties(Context context) { 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(); if (propsFile.isFile() && propsFile.canRead()) { try (FileInputStream in = new FileInputStream(propsFile)) { @@ -144,10 +149,57 @@ final class TermuxPreferences { } mBackIsEscape = "escape".equals(props.getProperty("back-key", "back")); + + shortcuts.clear(); + parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props); + parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props); + parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props); + parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props); } catch (Exception e) { Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show(); Log.e("termux", "Error loading props", e); } } + 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 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+"); + 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+"); + return; + } else { + codePoint = Character.toCodePoint(input.charAt(1), c); + } + } + shortcuts.add(new KeyboardShortcut(codePoint, shortcutAction)); + } + } diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 97fa02c2..05714619 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -352,4 +352,9 @@ public final class TermuxService extends Service implements SessionChangedCallba if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session); } + @Override + public void onColorsChanged(TerminalSession session) { + if (mSessionChangeCallback != null) mSessionChangeCallback.onColorsChanged(session); + } + } diff --git a/app/src/main/java/com/termux/terminal/TerminalEmulator.java b/app/src/main/java/com/termux/terminal/TerminalEmulator.java index 36110974..5ae7e84e 100644 --- a/app/src/main/java/com/termux/terminal/TerminalEmulator.java +++ b/app/src/main/java/com/termux/terminal/TerminalEmulator.java @@ -1816,6 +1816,7 @@ public final class TerminalEmulator { return; } else { mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i)); + mSession.onColorsChanged(); colorIndex = -1; parsingPairStart = -1; } @@ -1851,6 +1852,7 @@ public final class TerminalEmulator { + String.format(Locale.US, "%04x", b) + bellOrStringTerminator); } else { mColors.tryParseColor(specialIndex, colorSpec); + mSession.onColorsChanged(); } specialIndex++; 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. if (textParameter.isEmpty()) { mColors.reset(); + mSession.onColorsChanged(); } else { int lastIndex = 0; for (int charIndex = 0;; charIndex++) { @@ -1885,6 +1888,7 @@ public final class TerminalEmulator { try { int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex)); mColors.reset(colorToReset); + mSession.onColorsChanged(); if (endOfInput) break; charIndex++; lastIndex = charIndex; @@ -1899,6 +1903,7 @@ public final class TerminalEmulator { case 111: // Reset background color. case 112: // Reset cursor color. mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110)); + mSession.onColorsChanged(); break; case 119: // Reset highlight color. break; @@ -2273,6 +2278,7 @@ public final class TerminalEmulator { mUtf8Index = mUtf8ToFollow = 0; mColors.reset(); + mSession.onColorsChanged(); } public String getSelectedText(int x1, int y1, int x2, int y2) { diff --git a/app/src/main/java/com/termux/terminal/TerminalOutput.java b/app/src/main/java/com/termux/terminal/TerminalOutput.java index e2c4c1a8..3266779f 100644 --- a/app/src/main/java/com/termux/terminal/TerminalOutput.java +++ b/app/src/main/java/com/termux/terminal/TerminalOutput.java @@ -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. */ public abstract void onBell(); + public abstract void onColorsChanged(); + } diff --git a/app/src/main/java/com/termux/terminal/TerminalSession.java b/app/src/main/java/com/termux/terminal/TerminalSession.java index c7ecec3b..4df0d3fa 100644 --- a/app/src/main/java/com/termux/terminal/TerminalSession.java +++ b/app/src/main/java/com/termux/terminal/TerminalSession.java @@ -41,6 +41,9 @@ public final class TerminalSession extends TerminalOutput { void onClipboardText(TerminalSession session, String text); void onBell(TerminalSession session); + + void onColorsChanged(TerminalSession session); + } private static FileDescriptor wrapFileDescriptor(int fileDescriptor) { @@ -329,4 +332,11 @@ public final class TerminalSession extends TerminalOutput { mChangeCallback.onBell(this); } + @Override + public void onColorsChanged() { + mChangeCallback.onColorsChanged(this); + } + + public int getPid() { return mShellPid; } + } diff --git a/app/src/main/java/com/termux/view/TerminalKeyListener.java b/app/src/main/java/com/termux/view/TerminalKeyListener.java index 15350749..a1f801da 100644 --- a/app/src/main/java/com/termux/view/TerminalKeyListener.java +++ b/app/src/main/java/com/termux/view/TerminalKeyListener.java @@ -1,8 +1,11 @@ package com.termux.view; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ScaleGestureDetector; +import com.termux.terminal.TerminalSession; + /** * Input and scale listener which may be set on a {@link TerminalView} through * {@link TerminalView#setOnKeyListener(TerminalKeyListener)}. @@ -21,4 +24,14 @@ public interface TerminalKeyListener { 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); + } diff --git a/app/src/main/java/com/termux/view/TerminalRenderer.java b/app/src/main/java/com/termux/view/TerminalRenderer.java index 3f8eca0a..9f56c5d5 100644 --- a/app/src/main/java/com/termux/view/TerminalRenderer.java +++ b/app/src/main/java/com/termux/view/TerminalRenderer.java @@ -64,8 +64,7 @@ final class TerminalRenderer { final TerminalBuffer screen = mEmulator.getScreen(); final int[] palette = mEmulator.mColors.mCurrentColors; - int fillColor = palette[reverseVideo ? TextStyle.COLOR_INDEX_FOREGROUND : TextStyle.COLOR_INDEX_BACKGROUND]; - canvas.drawColor(fillColor, PorterDuff.Mode.SRC); + if (reverseVideo) canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC); float heightOffset = mFontLineSpacingAndAscent; for (int row = topRow; row < endRow; row++) { diff --git a/app/src/main/java/com/termux/view/TerminalView.java b/app/src/main/java/com/termux/view/TerminalView.java index f1fb3594..49bbd387 100644 --- a/app/src/main/java/com/termux/view/TerminalView.java +++ b/app/src/main/java/com/termux/view/TerminalView.java @@ -10,7 +10,6 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; -import android.media.AudioManager; import android.os.Build; import android.text.InputType; import android.text.TextUtils; @@ -34,28 +33,15 @@ import com.termux.R; import com.termux.terminal.EmulatorDebug; import com.termux.terminal.KeyHandler; import com.termux.terminal.TerminalBuffer; -import com.termux.terminal.TerminalColors; import com.termux.terminal.TerminalEmulator; 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}. */ public final class TerminalView extends View { /** Log view key and IME events. */ 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}. */ TerminalSession 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. */ 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; int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1; 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 // 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 - // word mode... Using TYPE_TEXT_VARIATION_VISIBLE_PASSWORD fixes that. + // Adding TYPE_TEXT_FLAG_NO_SUGGESTIONS fixes Pinyin Input and removes the row of numbers + // 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 // "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) { - @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 public boolean finishComposingText() { if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()"); commitText(getEditable(), 0); + + // Clear the editable. + getEditable().clear(); + return true; } - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); if (mEmulator == null) return true; final int textLengthInChars = text.length(); for (int i = 0; i < textLengthInChars; i++) { @@ -296,9 +274,24 @@ public final class TerminalView extends View { } else { 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 @@ -313,7 +306,18 @@ public final class TerminalView extends View { 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 @@ -378,6 +382,12 @@ public final class TerminalView extends View { updateSize(); } + public void setTypeface(Typeface newTypeface) { + mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface); + updateSize(); + invalidate(); + } + @Override public boolean onCheckIsTextEditor() { 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 (mEmulator == null) return true; - int metaState = event.getMetaState(); - 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)) { + if (mOnKeyListener.onKeyDown(keyCode, event, mTermSession)) { invalidate(); return true; } else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) { 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 (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT; if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT; @@ -608,15 +621,12 @@ public final class TerminalView extends View { + leftAltDownFromEvent + ")"); } - boolean controlDown = controlDownFromEvent || mVirtualControlKeyDown; - boolean altDown = leftAltDownFromEvent; - if (mModifiers != null) { - if (mModifiers.readControlButton()) controlDown = true; - if (mModifiers.readAltButton()) altDown = true; - } + final boolean controlDown = controlDownFromEvent || mOnKeyListener.readControlKey(); + final boolean altDown = leftAltDownFromEvent || mOnKeyListener.readAltKey(); - int resultingKeyCode = -1; // Set if virtual key causes this to be translated to key event. - if (controlDown) { + if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return; + + if (controlDown) { if (codePoint >= 'a' && codePoint <= 'z') { codePoint = codePoint - 'a' + 1; } else if (codePoint >= 'A' && codePoint <= 'Z') { @@ -635,87 +645,29 @@ public final class TerminalView extends View { codePoint = 31; } else if (codePoint == '8') { 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; + if (codePoint > -1) { + // 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 '0': - resultingKeyCode = KeyEvent.KEYCODE_F10; + case 0x02CB: // MODIFIER LETTER GRAVE ACCENT. + codePoint = 0x0060; // GRAVE ACCENT (`). 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); + case 0x02C6: // MODIFIER LETTER CIRCUMFLEX ACCENT. + codePoint = 0x005E; // CIRCUMFLEX ACCENT (^). break; } - } - if (codePoint > -1) { - if (resultingKeyCode > -1) { - 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); - } - } - } + // 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. */ 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 (mEmulator == null) return true; - if (handleVirtualKeys(keyCode, event, false)) { + if (mOnKeyListener.onKeyUp(keyCode, event)) { invalidate(); return true; } else if (event.isSystem()) { @@ -751,49 +703,6 @@ public final class TerminalView extends View { 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 * hierarchy, you're called with the old values of 0. diff --git a/app/src/main/res/layout/extra_keys_right.xml b/app/src/main/res/layout/extra_keys_right.xml index cd7fc451..7a22b4df 100644 --- a/app/src/main/res/layout/extra_keys_right.xml +++ b/app/src/main/res/layout/extra_keys_right.xml @@ -9,7 +9,6 @@ android:inputType="text" android:singleLine="true" android:textColor="@android:color/white" - android:backgroundTint="@android:color/transparent" android:paddingTop="0dp" android:textCursorDrawable="@null" android:paddingBottom="0dp"