diff --git a/float/src/main/java/com/termux/window/TermuxFloatKeyListener.java b/float/src/main/java/com/termux/window/TermuxFloatKeyListener.java new file mode 100644 index 00000000..3fed8133 --- /dev/null +++ b/float/src/main/java/com/termux/window/TermuxFloatKeyListener.java @@ -0,0 +1,214 @@ +package com.termux.window; + +import android.content.Context; +import android.media.AudioManager; +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; + +public class TermuxFloatKeyListener implements TerminalKeyListener { + + private final TermuxFloatView view; + /** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */ + boolean mVirtualControlKeyDown, mVirtualFnKeyDown; + + public TermuxFloatKeyListener(TermuxFloatView view) { + this.view = view; + } + + @Override + public float onScale(float scale) { + if (scale < 0.9f || scale > 1.1f) { + boolean increase = scale > 1.f; + ((TermuxFloatService) view.getContext()).changeFontSize(increase); + return 1.0f; + } + return scale; + } + + @Override + public boolean onLongPress(MotionEvent event) { + view.updateLongPressMode(true); + view.initialX = view.layoutParams.x; + view.initialY = view.layoutParams.y; + view.initialTouchX = event.getRawX(); + view.initialTouchY = event.getRawY(); + return true; + } + + @Override + public void onSingleTapUp(MotionEvent e) { + // Do nothing. + } + + @Override + public boolean shouldBackButtonBeMappedToEscape() { + return false; + } + + @Override + public void copyModeChanged(boolean copyMode) { + + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { + if (handleVirtualKeys(keyCode, e, true)) return true; + + if (e.isCtrlPressed() && e.isAltPressed()) { + // Get the unmodified code point: + int unicodeChar = e.getUnicodeChar(0); + + if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) { + // TODO: Toggle minimized or not. + } else if (unicodeChar == 'f'/* full screen */) { + // TODO: Toggle full screen. + } else if (unicodeChar == 'k'/* keyboard */) { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + return true; + } + + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent e) { + return handleVirtualKeys(keyCode, e, false); + } + + + @Override + public boolean readControlKey() { + return mVirtualControlKeyDown; + } + + @Override + public boolean readAltKey() { + return false; + } + + @Override + public boolean onCodePoint(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': + resultingCodePoint = '~'; + 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) view.getContext().getSystemService(Context.AUDIO_SERVICE); + audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI); + 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; + } + + 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/float/src/main/java/com/termux/window/TermuxFloatView.java b/float/src/main/java/com/termux/window/TermuxFloatView.java index 2fc0e206..f57da1fb 100644 --- a/float/src/main/java/com/termux/window/TermuxFloatView.java +++ b/float/src/main/java/com/termux/window/TermuxFloatView.java @@ -1,16 +1,11 @@ package com.termux.window; -import com.termux.terminal.TerminalSession; -import com.termux.view.TerminalKeyListener; -import com.termux.view.TerminalView; - import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Point; import android.util.AttributeSet; import android.view.Gravity; -import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; @@ -19,6 +14,8 @@ import android.view.inputmethod.InputMethodManager; import android.widget.LinearLayout; import android.widget.Toast; +import com.termux.view.TerminalView; + public class TermuxFloatView extends LinearLayout { public static final float ALPHA_FOCUS = 0.9f; @@ -37,13 +34,15 @@ public class TermuxFloatView extends LinearLayout { Toast mLastToast; private boolean withFocus = true; - private int initialX; - private int initialY; - private float initialTouchX; - private float initialTouchY; + int initialX; + int initialY; + float initialTouchX; + float initialTouchY; boolean isInLongPressState; + final int[] location = new int[2]; + final ScaleGestureDetector mScaleDetector = new ScaleGestureDetector(getContext(), new OnScaleGestureListener() { private static final int MIN_SIZE = 50; @@ -86,69 +85,7 @@ public class TermuxFloatView extends LinearLayout { public void initializeFloatingWindow() { mTerminalView = (TerminalView) findViewById(R.id.terminal_view); - - mTerminalView.setOnKeyListener(new TerminalKeyListener() { - @Override - public float onScale(float scale) { - if (scale < 0.9f || scale > 1.1f) { - boolean increase = scale > 1.f; - ((TermuxFloatService) getContext()).changeFontSize(increase); - return 1.0f; - } - return scale; - } - - @Override - public boolean onLongPress(MotionEvent event) { - updateLongPressMode(true); - initialX = layoutParams.x; - initialY = layoutParams.y; - initialTouchX = event.getRawX(); - initialTouchY = event.getRawY(); - return true; - } - - @Override - public void onSingleTapUp(MotionEvent e) { - // Do nothing. - } - - @Override - public boolean shouldBackButtonBeMappedToEscape() { - return true; - } - - @Override - public void copyModeChanged(boolean copyMode) { - - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { - return false; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent e) { - return false; - } - - @Override - public boolean readControlKey() { - return false; - } - - @Override - public boolean readAltKey() { - return false; - } - - @Override - public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { - return false; - } - - }); + mTerminalView.setOnKeyListener(new TermuxFloatKeyListener(this)); } @Override @@ -185,32 +122,29 @@ public class TermuxFloatView extends LinearLayout { @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (isInLongPressState) return true; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if ((event.getMetaState() & (KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON)) != 0) { - updateLongPressMode(true); - initialX = layoutParams.x; - initialY = layoutParams.y; - initialTouchX = event.getRawX(); - initialTouchY = event.getRawY(); - return true; - } - // FIXME: params.x and params.y are outdated when snapping to end of screen, where the movement stops but x - // and y are wrong. - float touchX = event.getRawX(); - float touchY = event.getRawY(); - boolean clickedInside = (touchX >= layoutParams.x) && (touchX <= (layoutParams.x + layoutParams.width)) && (touchY >= layoutParams.y) - && (touchY <= (layoutParams.y + layoutParams.height)); - if (withFocus != clickedInside) { - changeFocus(clickedInside); - } else if (clickedInside) { - // When clicking inside, show keyboard if the user has hidden it: - showTouchKeyboard(); - } - } + + getLocationOnScreen(location); + int x = layoutParams.x; // location[0]; + int y = layoutParams.y; // location[1]; + float touchX = event.getRawX(); + float touchY = event.getRawY(); + boolean clickedInside = (touchX >= x) && (touchX <= (x + layoutParams.width)) && (touchY >= y) && (touchY <= (y + layoutParams.height)); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (!clickedInside) changeFocus(false); + break; + case MotionEvent.ACTION_UP: + if (clickedInside) { + changeFocus(true); + showTouchKeyboard(); + } + break; + } return false; } - private void showTouchKeyboard() { + void showTouchKeyboard() { mTerminalView.post(new Runnable() { @Override public void run() { @@ -219,10 +153,10 @@ public class TermuxFloatView extends LinearLayout { }); } - private void updateLongPressMode(boolean newValue) { + void updateLongPressMode(boolean newValue) { isInLongPressState = newValue; setBackgroundResource(newValue ? R.drawable.floating_window_background_resize : R.drawable.floating_window_background); - setAlpha(newValue ? ALPHA_MOVING : ALPHA_FOCUS); + setAlpha(newValue ? ALPHA_MOVING : (withFocus ? ALPHA_FOCUS : ALPHA_NOT_FOCUS)); if (newValue) { Toast toast = Toast.makeText(getContext(), R.string.after_long_press, Toast.LENGTH_SHORT); toast.setGravity(Gravity.CENTER, 0, 0); @@ -254,12 +188,15 @@ public class TermuxFloatView extends LinearLayout { } /** Visually indicate focus and show the soft input as needed. */ - private void changeFocus(boolean newFocus) { - withFocus = newFocus; + void changeFocus(boolean newFocus) { + if (newFocus == withFocus) { + if (newFocus) showTouchKeyboard(); + return; + } + withFocus = newFocus; layoutParams.flags = computeLayoutFlags(withFocus); mWindowManager.updateViewLayout(this, layoutParams); setAlpha(newFocus ? ALPHA_FOCUS : ALPHA_NOT_FOCUS); - if (newFocus) showTouchKeyboard(); } /** Show a toast and dismiss the last one if still visible. */