diff --git a/app/build.gradle b/app/build.gradle index 9bc38471b..5ea069b31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 30 defaultConfig { applicationId "com.termux.x11" minSdkVersion 24 @@ -59,4 +59,6 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.drawerlayout:drawerlayout:1.1.1' + implementation 'com.termux.termux-app:termux-shared:master-SNAPSHOT' + implementation "com.google.guava:guava:24.1-jre" } diff --git a/app/src/main/java/com/termux/x11/AdditionalKeyboardView.java b/app/src/main/java/com/termux/x11/AdditionalKeyboardView.java deleted file mode 100644 index 5436afa54..000000000 --- a/app/src/main/java/com/termux/x11/AdditionalKeyboardView.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.termux.x11; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Rect; -import android.preference.PreferenceManager; -import androidx.appcompat.widget.AppCompatTextView; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; -import android.widget.HorizontalScrollView; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.view.SurfaceView; -import java.util.HashMap; -import java.util.Map; - -@SuppressWarnings({"unused", "FieldCanBeLocal"}) -public class AdditionalKeyboardView extends HorizontalScrollView implements ViewTreeObserver.OnGlobalLayoutListener { - private final static int KEYCODE_BASE = 300; - public final static int PREFERENCES_KEY = KEYCODE_BASE + 1; - public final static int KEY_HEIGHT_DP = 35; - - private boolean softKbdVisible; - private Context ctx; - private View targetView = null; - private View.OnKeyListener targetListener = null; - private int density; - private LinearLayout root; - public AdditionalKeyboardView(Context context) { - super(context); - init(context); - } - public AdditionalKeyboardView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - - @SuppressLint("SetTextI18n") - private void init(Context context) { - ctx = context; - density = (int) context.getResources().getDisplayMetrics().density; - - getViewTreeObserver().addOnGlobalLayoutListener(this); - - setBackgroundColor(0xFF000000); - LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, KEY_HEIGHT_DP * density); - root = new LinearLayout(context); - root.setLayoutParams(lp); - root.setOrientation(LinearLayout.HORIZONTAL); - addView(root); - } - - @Override - public void onGlobalLayout() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); - if (!preferences.getBoolean("showAdditionalKbd", true)) { - if (getVisibility() != View.GONE) - setVisibility(View.GONE); - return; - } else { - - int visibility = View.VISIBLE; - softKbdVisible = (visibility == View.VISIBLE); - - if (!softKbdVisible) - setVisibility(visibility); - } - } - - public void reload(int[] keys, View TargetView, View.OnKeyListener TargetListener) { - targetView = TargetView; - targetListener = TargetListener; - root.removeAllViews(); - LayoutParams lp = new LayoutParams(60 * density,LayoutParams.MATCH_PARENT); - lp.setMargins(density*3, 0, density*3, 0); - for (int keyCode : keys) { - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - Key key = new Key(ctx, keyCode); - root.addView(key, lp); - } - } - } - - private class Key extends AppCompatTextView implements View.OnClickListener, View.OnTouchListener { - private static final int TEXT_COLOR = 0xFFFFFFFF; - private static final int BUTTON_COLOR = 0x0000FFFF; - private static final int INTERESTING_COLOR = 0xFF80DEEA; - private static final int BUTTON_PRESSED_COLOR = 0x7FFFFFFF; - /** - * Keys are displayed in a natural looking way, like "▶" for "RIGHT" - */ - final Map keyCodesForString = new HashMap() {{ - put(KeyEvent.KEYCODE_ESCAPE, "ESC"); - put(KeyEvent.KEYCODE_MOVE_HOME, "HOME"); - put(KeyEvent.KEYCODE_MOVE_END, "END"); - put(KeyEvent.KEYCODE_PAGE_UP, "PGUP"); - put(KeyEvent.KEYCODE_PAGE_DOWN, "PGDN"); - put(KeyEvent.KEYCODE_CTRL_LEFT, "CTRL"); - put(KeyEvent.KEYCODE_ALT_LEFT, "ALT"); - put(KeyEvent.KEYCODE_INSERT, "INS"); - put(KeyEvent.KEYCODE_FORWARD_DEL, "⌦"); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand - put(KeyEvent.KEYCODE_DEL, "⌫"); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand - put(KeyEvent.KEYCODE_DPAD_UP, "▲"); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE - put(KeyEvent.KEYCODE_DPAD_LEFT, "◀"); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE - put(KeyEvent.KEYCODE_DPAD_RIGHT, "▶"); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE - put(KeyEvent.KEYCODE_DPAD_DOWN, "▼"); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE - put(KeyEvent.KEYCODE_ENTER, "↲"); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS - put(KeyEvent.KEYCODE_TAB, "↹"); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR - put(KeyEvent.KEYCODE_MINUS, "―"); // U+2015 ― HORIZONTAL BAR - put(-1, "⚙"); // U+2699 ⚙ GEAR - }}; - - void sendKey(int keyCode, boolean down) { - if (targetListener == null || targetView == null) { - Log.e("AdditionalKeyboardView", "target view or target listener is unset"); - return; - } - - targetListener.onKey(targetView, keyCode, new KeyEvent((down?KeyEvent.ACTION_DOWN:KeyEvent.ACTION_UP), keyCode)); - } - - boolean toggle; - boolean checked = false; - int kc; - public Key(Context context, int keyCode) { - super(context); - kc = keyCode; - switch (keyCode) { - case KeyEvent.KEYCODE_CTRL_LEFT: - case KeyEvent.KEYCODE_CTRL_RIGHT: - case KeyEvent.KEYCODE_ALT_LEFT: - case KeyEvent.KEYCODE_ALT_RIGHT: - case KeyEvent.KEYCODE_SHIFT_LEFT: - case KeyEvent.KEYCODE_SHIFT_RIGHT: - toggle = true; - break; - default: - toggle = false; - } - - - setTextColor(TEXT_COLOR); - setBackgroundColor(BUTTON_COLOR); - String text = keyCodesForString.get(keyCode); - float textWidth = getPaint().measureText(text); - setWidth((int) (textWidth + 20 * density)); - setText(text); - setGravity(Gravity.CENTER); - setOnClickListener(this); - setOnTouchListener(this); - } - - @Override - public void onClick(View v) { - if (toggle) { - checked = !checked; - if (checked) { - setTextColor(INTERESTING_COLOR); - setBackgroundColor(BUTTON_PRESSED_COLOR); - sendKey(kc, true); - } else { - setTextColor(TEXT_COLOR); - setBackgroundColor(BUTTON_COLOR); - sendKey(kc, false); - } - } - } - - private Rect rect; - @Override - public boolean onTouch(View v, MotionEvent e) { - switch(e.getAction()) { - case MotionEvent.ACTION_BUTTON_PRESS: - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_DOWN: - setTextColor(INTERESTING_COLOR); - setBackgroundColor(BUTTON_PRESSED_COLOR); - if (!toggle) sendKey(kc, true); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_BUTTON_RELEASE: - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_UP: - if (!toggle) { - setTextColor(TEXT_COLOR); - setBackgroundColor(BUTTON_COLOR); - sendKey(kc, false); - } - break; - default: - break; - } - return false; - } - } -} diff --git a/app/src/main/java/com/termux/x11/Fullscreenworkaround.java b/app/src/main/java/com/termux/x11/Fullscreenworkaround.java new file mode 100644 index 000000000..c0639ef89 --- /dev/null +++ b/app/src/main/java/com/termux/x11/Fullscreenworkaround.java @@ -0,0 +1,55 @@ +package com.termux.x11; + +import android.view.ViewTreeObserver; +import android.graphics.Rect; +import android.widget.FrameLayout; +import android.view.View; +import android.app.Activity; + +public class Fullscreenworkaround { + + // For more information, see https://issuetracker.google.com/issues/36911528 + // To use this class, simply invoke assistActivity() on an Activity that already has its content view set. + + public static void assistActivity (Activity activity) { + new Fullscreenworkaround(activity); + } + + private View mChildOfContent; + private int usableHeightPrevious; + private FrameLayout.LayoutParams frameLayoutParams; + + private Fullscreenworkaround(Activity activity) { + FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); + mChildOfContent = content.getChildAt(0); + mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + public void onGlobalLayout() { + possiblyResizeChildOfContent(); + } + }); + frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); + } + + private void possiblyResizeChildOfContent() { + int usableHeightNow = computeUsableHeight(); + if (usableHeightNow != usableHeightPrevious) { + int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); + int heightDifference = usableHeightSansKeyboard - usableHeightNow; + if (heightDifference > (usableHeightSansKeyboard/4)) { + // keyboard probably just became visible + frameLayoutParams.height = usableHeightSansKeyboard - heightDifference; + } else { + // keyboard probably just became hidden + frameLayoutParams.height = usableHeightSansKeyboard; + } + mChildOfContent.requestLayout(); + usableHeightPrevious = usableHeightNow; + } + } + + private int computeUsableHeight() { + Rect r = new Rect(); + mChildOfContent.getWindowVisibleDisplayFrame(r); + return (r.bottom - r.top); + } +} diff --git a/app/src/main/java/com/termux/x11/LorieService.java b/app/src/main/java/com/termux/x11/LorieService.java index ff0a9b25e..2c64ff41f 100644 --- a/app/src/main/java/com/termux/x11/LorieService.java +++ b/app/src/main/java/com/termux/x11/LorieService.java @@ -220,7 +220,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { } onPreferencesChanged(); - return START_REDELIVER_INTENT; } @@ -399,7 +398,7 @@ public boolean onKey(View v, int keyCode, KeyEvent e) { svc.pointerButton(TouchParser.BTN_RIGHT, (e.getAction() == KeyEvent.ACTION_DOWN) ? TouchParser.ACTION_DOWN : TouchParser.ACTION_UP); rightPressed = (e.getAction() == KeyEvent.ACTION_DOWN); } else if (e.getAction() == KeyEvent.ACTION_UP) { - if (act.kbd!=null) act.kbd.requestFocus(); + if (act.getTerminalToolbarViewPager()!=null) act.getTerminalToolbarViewPager().requestFocus(); KeyboardUtils.toggleKeyboardVisibility(act); } return true; @@ -505,28 +504,28 @@ public static void unzip(InputStream zipStream, File targetDirectory) throws IOE private void windowChanged(Surface s, int w, int h, int pw, int ph) {windowChanged(compositor, s, w, h, pw, ph);} private native void windowChanged(long compositor, Surface surface, int width, int height, int mmWidth, int mmHeight); - + private void touchDown(int id, float x, float y) { touchDown(compositor, id, (int) x, (int) y); } private native void touchDown(long compositor, int id, int x, int y); - + private void touchMotion(int id, float x, float y) { touchMotion(compositor, id, (int) x, (int) y); } private native void touchMotion(long compositor, int id, int x, int y); - + private void touchUp(int id) { touchUp(compositor, id); } private native void touchUp(long compositor, int id); - + private void touchFrame() { touchFrame(compositor); } private native void touchFrame(long compositor); - + private void pointerMotion(float x, float y) { pointerMotion(compositor, (int) x, (int) y); } private native void pointerMotion(long compositor, int x, int y); - + private void pointerScroll(int axis, float value) { pointerScroll(compositor, axis, value); } private native void pointerScroll(long compositor, int axis, float value); - + private void pointerButton(int button, int type) { pointerButton(compositor, button, type); } private native void pointerButton(long compositor, int button, int type); - + private void keyboardKey(int key, int type, int shift, String characters) {keyboardKey(compositor, key, type, shift, characters);} private native void keyboardKey(long compositor, int key, int type, int shift, String characters); @@ -534,7 +533,6 @@ public static void unzip(InputStream zipStream, File targetDirectory) throws IOE private native void passWaylandFD(long compositor, int fd); private native long createLorieThread(); - private native void terminate(long compositor); public static native void startLogcatForFd(int fd); diff --git a/app/src/main/java/com/termux/x11/MainActivity.java b/app/src/main/java/com/termux/x11/MainActivity.java index 46ed1b601..ba94a16e7 100644 --- a/app/src/main/java/com/termux/x11/MainActivity.java +++ b/app/src/main/java/com/termux/x11/MainActivity.java @@ -4,12 +4,15 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.os.Build; import android.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.viewpager.widget.ViewPager; + import android.os.Bundle; +import android.os.Build; + import android.view.KeyEvent; import android.view.PointerIcon; import android.view.SurfaceView; @@ -17,28 +20,35 @@ import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; + +import android.view.ViewGroup; +import android.view.LayoutInflater; + +import android.widget.Toast; import android.widget.FrameLayout; +import com.termux.shared.termux.extrakeys.ExtraKeysView; + +import com.termux.x11.TermuxAppSharedProperties; +import com.termux.x11.TerminalExtraKeys; +import com.termux.x11.TerminalToolbarViewPager; +import com.termux.x11.Fullscreenworkaround; + public class MainActivity extends AppCompatActivity { - private static int[] keys = { - KeyEvent.KEYCODE_ESCAPE, - KeyEvent.KEYCODE_TAB, - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_RIGHT, - }; - - AdditionalKeyboardView kbd; + private TermuxAppSharedProperties mProperties; + private int mTerminalToolbarDefaultHeight; + + ExtraKeysView mExtraKeysView; FrameLayout frm; @Override + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - LorieService.setMainActivity(this); + mProperties = TermuxAppSharedProperties.build(this); + + LorieService.setMainActivity(this); LorieService.start(LorieService.ACTION_START_FROM_ACTIVITY); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN| @@ -48,14 +58,15 @@ protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main_activity); - kbd = findViewById(R.id.additionalKbd); frm = findViewById(R.id.frame); + setTerminalToolbarView(); + toggleTerminalToolbar(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) getWindow(). getDecorView(). setPointerIcon(PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); - Intent i = getIntent(); if (i != null && i.getStringExtra(LorieService.LAUNCHED_BY_COMPATION) == null) { LorieService.sendRunCommand(this); @@ -67,7 +78,7 @@ protected void onCreate(Bundle savedInstanceState) { public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); - if (newConfig.orientation != orientation && kbd != null && kbd.getVisibility() == View.VISIBLE) { + if (newConfig.orientation != orientation && this.getTerminalToolbarViewPager() != null && this.getTerminalToolbarViewPager().getVisibility() == View.VISIBLE) { InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); View view = getCurrentFocus(); if (view == null) { @@ -79,11 +90,65 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) { orientation = newConfig.orientation; } + public TermuxAppSharedProperties getProperties() { + return mProperties; + } + + public ExtraKeysView getExtraKeysView() { + return mExtraKeysView; + } + + public void setExtraKeysView(ExtraKeysView extraKeysView) { + mExtraKeysView = extraKeysView; + } + public void onLorieServiceStart(LorieService instance) { + instance.setListeners(this.getlorieView()); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + } + + public SurfaceView getlorieView() { SurfaceView lorieView = findViewById(R.id.lorieView); + return lorieView; + } + + public ViewPager getTerminalToolbarViewPager() { + return (ViewPager) findViewById(R.id.terminal_toolbar_view_pager); + } + + private void setTerminalToolbarView() { + final ViewPager terminalToolbarViewPager = getTerminalToolbarViewPager(); + + ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams(); + mTerminalToolbarDefaultHeight = layoutParams.height; + + setLorieToolbarHeight(); + + terminalToolbarViewPager.setAdapter(new TerminalToolbarViewPager.PageAdapter(this, LorieService.getOnKeyListener())); + terminalToolbarViewPager.addOnPageChangeListener(new TerminalToolbarViewPager.OnPageChangeListener(this, terminalToolbarViewPager)); + } + + private void setLorieToolbarHeight() { + final ViewPager terminalToolbarViewPager = getTerminalToolbarViewPager(); + if (terminalToolbarViewPager == null) return; - instance.setListeners(lorieView); - kbd.reload(keys, lorieView, LorieService.getOnKeyListener()); + ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams(); + layoutParams.height = (int) Math.round(mTerminalToolbarDefaultHeight * + (mProperties.getExtraKeysInfo() == null ? 0 : mProperties.getExtraKeysInfo().getMatrix().length) * + mProperties.getTerminalToolbarHeightScaleFactor()); + terminalToolbarViewPager.setLayoutParams(layoutParams); + } + + + public void toggleTerminalToolbar() { + final ViewPager terminalToolbarViewPager = getTerminalToolbarViewPager(); + if (terminalToolbarViewPager == null) return; + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean showNow = preferences.getBoolean("Toolbar", true); + + terminalToolbarViewPager.setVisibility(showNow ? View.VISIBLE : View.GONE); + findViewById(R.id.terminal_toolbar_view_pager).requestFocus(); } @Override @@ -92,10 +157,33 @@ public void onWindowFocusChanged(boolean hasFocus) super.onWindowFocusChanged(hasFocus); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); Window window = getWindow(); + View decorView = window.getDecorView(); + + if (hasFocus && preferences.getBoolean("fullscreen", false)) + { + window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + decorView.setSystemUiVisibility(0); + } if (preferences.getBoolean("Reseed", true)) { - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + if (preferences.getBoolean("fullscreen", false)) + { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } else { + // Taken from Stackoverflow answer https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible/7509285# + Fullscreenworkaround.assistActivity(this); + } } else { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN| WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); @@ -115,22 +203,15 @@ public void onUserLeaveHint () { @Override public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); if (isInPictureInPictureMode) { - if (kbd.getVisibility() != View.GONE) - kbd.setVisibility(View.GONE); + if (this.getTerminalToolbarViewPager().getVisibility() != View.GONE) + this.getTerminalToolbarViewPager().setVisibility(View.GONE); frm.setPadding(0,0,0,0); return; } else { - if (kbd.getVisibility() != View.VISIBLE) - if (preferences.getBoolean("showAdditionalKbd", true)) { - kbd.setVisibility(View.VISIBLE); - int paddingDp = 35; - float density = this.getResources().getDisplayMetrics().density; - int paddingPixel = (int)(paddingDp * density); - frm.setPadding(0,0,0,paddingPixel); - } + if (this.getTerminalToolbarViewPager().getVisibility() != View.VISIBLE) + this.getTerminalToolbarViewPager().setVisibility(View.VISIBLE); return; } } diff --git a/app/src/main/java/com/termux/x11/TerminalExtraKeys.java b/app/src/main/java/com/termux/x11/TerminalExtraKeys.java new file mode 100644 index 000000000..01a0f178f --- /dev/null +++ b/app/src/main/java/com/termux/x11/TerminalExtraKeys.java @@ -0,0 +1,193 @@ +package com.termux.x11; + +import android.view.KeyEvent; +import android.view.View; +import android.widget.Button; +import android.view.SurfaceView; +import android.annotation.SuppressLint; +import android.view.Gravity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.app.PendingIntent; +import android.content.Intent; +import android.view.KeyEvent; +import android.view.KeyCharacterMap; +import android.text.TextUtils; +import android.app.ActivityManager; +import android.app.Activity; + +import java.lang.Character; + +import androidx.annotation.NonNull; +import androidx.drawerlayout.widget.DrawerLayout; + +import com.termux.shared.termux.extrakeys.ExtraKeyButton; +import com.termux.shared.termux.extrakeys.ExtraKeysView; +import com.termux.shared.termux.extrakeys.SpecialButton; + +import static com.termux.shared.termux.extrakeys.ExtraKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS; + +public class TerminalExtraKeys implements ExtraKeysView.IExtraKeysView { + + private final View.OnKeyListener mEventListener; + private final MainActivity act; + private final ExtraKeysView mExtraKeysView; + + private int metaAltState = 0; + + public TerminalExtraKeys(@NonNull View.OnKeyListener eventlistener, MainActivity mact, ExtraKeysView extrakeysview) { + mEventListener = eventlistener; + act = mact; + mExtraKeysView = extrakeysview; + } + + private final KeyCharacterMap mVirtualKeyboardKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + static final String ACTION_START_PREFERENCES_ACTIVITY = "com.termux.x11.start_preferences_activity"; + + @Override + public void onAnyExtraKeyButtonClick(View view, @NonNull ExtraKeyButton buttonInfo, Button button) { + if (isSpecialButton(buttonInfo)) { + if (mLongPressCount > 0) return; + SpecialButtonState state = mSpecialButtons.get(SpecialButton.valueOf(buttonInfo.getKey())); + if (state == null) return; + super.onExtraKeyButtonClick(view, buttonInfo, button); + + // Toggle active state and disable lock state if new state is not active + state.setIsActive(!state.isActive); + if (!state.isActive) + state.setIsLocked(false); + } else { + super.onExtraKeyButtonClick(view, buttonInfo, button); + } + } + + @Override + public void onExtraKeyButtonClick(View view, ExtraKeyButton buttonInfo, Button button) { + if (buttonInfo.isMacro()) { + String[] keys = buttonInfo.getKey().split(" "); + boolean ctrlDown = false; + boolean altDown = false; + boolean shiftDown = false; + boolean fnDown = false; + for (String key : keys) { + if (SpecialButton.CTRL.getKey().equals(key)) { + ctrlDown = true; + } else if (SpecialButton.ALT.getKey().equals(key)) { + altDown = true; + } else if (SpecialButton.SHIFT.getKey().equals(key)) { + shiftDown = true; + } else if (SpecialButton.FN.getKey().equals(key)) { + fnDown = true; + } else { + ctrlDown = false; + altDown = false; + shiftDown = false; + fnDown = false; + } + onLorieExtraKeyButtonClick(view, key, ctrlDown, altDown, shiftDown, fnDown); + } + } else { + onLorieExtraKeyButtonClick(view, buttonInfo.getKey(), false, false, false, false); + } + } + + protected void onTerminalExtraKeyButtonClick(View view, String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { + if (PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) { + Integer keyCode = PRIMARY_KEY_CODES_FOR_STRINGS.get(key); + if (keyCode == null) return; + int metaState = 0; + + if (ctrlDown) { + metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; + metaAltState |= KeyEvent.KEYCODE_CTRL_LEFT | KeyEvent.KEYCODE_CTRL_RIGHT; + } + + if (altDown) { + metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; + metaAltState |= KeyEvent.KEYCODE_ALT_LEFT | KeyEvent.KEYCODE_ALT_RIGHT; + } + + if (shiftDown) { + metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; + } + + if (fnDown) { + metaState |= KeyEvent.META_FUNCTION_ON; + } + + + mEventListener.onKey(act.getlorieView(), keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, metaState)); + mEventListener.onKey(act.getlorieView(), keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState)); + + } else { + // not a control char + key.codePoints().forEach(codePoint -> { + char ch[]; + ch = Character.toChars(codePoint); + KeyEvent[] events = mVirtualKeyboardKeyCharacterMap.getEvents(ch); + if (events != null) { + for (KeyEvent event : events) { + + Integer keyCode = event.getKeyCode(); + + if (metaAltState != 0) { + mEventListener.onKey(act.getlorieView(), keyCode, new KeyEvent(metaAltState, keyCode)); + } + + mEventListener.onKey(act.getlorieView(), keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + + if (metaAltState != 0) { + mEventListener.onKey(act.getlorieView(), keyCode, new KeyEvent(metaAltState, keyCode)); + metaAltState = 0; + } + } + } + }); + } + } + + @Override + public boolean performExtraKeyButtonHapticFeedback(View view, ExtraKeyButton buttonInfo, Button button) { + return false; + } + + public void paste(CharSequence input) { + KeyEvent[] events = mVirtualKeyboardKeyCharacterMap.getEvents(input.toString().toCharArray()); + if (events != null) { + for (KeyEvent event : events) { + Integer keyCode = event.getKeyCode(); + mEventListener.onKey(act.getlorieView(), keyCode, event); + } + } + } + + @SuppressLint("RtlHardcoded") + public void onLorieExtraKeyButtonClick(View view, String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { + if ("KEYBOARD".equals(key)) { + + if (act.getTerminalToolbarViewPager()!=null) { + act.getTerminalToolbarViewPager().requestFocus(); + KeyboardUtils.toggleKeyboardVisibility(act); + } + + } else if ("DRAWER".equals(key)) { + Intent preferencesIntent = new Intent(act, LoriePreferences.class); + preferencesIntent.setAction(ACTION_START_PREFERENCES_ACTIVITY); + act.startActivity(preferencesIntent); + + } else if ("PASTE".equals(key)) { + + ClipboardManager clipboard = (ClipboardManager) act.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clipData = clipboard.getPrimaryClip(); + + if (clipData != null) { + CharSequence pasted = clipData.getItemAt(0).coerceToText(act); + if (!TextUtils.isEmpty(pasted)) paste(pasted); + } + + } else { + onTerminalExtraKeyButtonClick(view, key, ctrlDown, altDown, shiftDown, fnDown); + } + } +} diff --git a/app/src/main/java/com/termux/x11/TerminalToolbarViewPager.java b/app/src/main/java/com/termux/x11/TerminalToolbarViewPager.java new file mode 100644 index 000000000..a6c7a2746 --- /dev/null +++ b/app/src/main/java/com/termux/x11/TerminalToolbarViewPager.java @@ -0,0 +1,105 @@ +package com.termux.x11; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.KeyEvent; +import android.view.KeyCharacterMap; + +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.termux.shared.termux.extrakeys.ExtraKeysView; +import com.termux.x11.TerminalExtraKeys; + +public class TerminalToolbarViewPager { + + public static class PageAdapter extends PagerAdapter { + + final MainActivity act; + private final View.OnKeyListener mEventListener; + private final KeyCharacterMap mVirtualKeyboardKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + + public PageAdapter(MainActivity activity, View.OnKeyListener listen) { + this.act = activity; + this.mEventListener = listen; + } + + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup collection, int position) { + LayoutInflater inflater = LayoutInflater.from(act); + View layout; + if (position == 0) { + layout = inflater.inflate(R.layout.view_terminal_toolbar_extra_keys, collection, false); + ExtraKeysView extraKeysView = (ExtraKeysView) layout; + extraKeysView.reload(act.getProperties().getExtraKeysInfo()); + extraKeysView.setExtraKeysViewClient(new TerminalExtraKeys(mEventListener, act, extraKeysView)); + act.setExtraKeysView(extraKeysView); + } else { + layout = inflater.inflate(R.layout.view_terminal_toolbar_text_input, collection, false); + final EditText editText = layout.findViewById(R.id.terminal_toolbar_text_input); + + editText.setOnEditorActionListener((v, actionId, event) -> { + String textToSend = editText.getText().toString(); + if (textToSend.length() == 0) textToSend = "\r"; + + KeyEvent[] events = mVirtualKeyboardKeyCharacterMap.getEvents(textToSend.toCharArray()); + for (KeyEvent evnt : events) { + Integer keyCode = evnt.getKeyCode(); + mEventListener.onKey(act.getlorieView(), keyCode, evnt); + } + + editText.setText(""); + return true; + }); + } + collection.addView(layout); + return layout; + } + + @Override + public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) { + collection.removeView((View) view); + } + + } + + + + public static class OnPageChangeListener extends ViewPager.SimpleOnPageChangeListener { + + final MainActivity act; + final ViewPager mTerminalToolbarViewPager; + + public OnPageChangeListener(MainActivity activity, ViewPager viewPager) { + this.act = activity; + this.mTerminalToolbarViewPager = viewPager; + } + + @Override + public void onPageSelected(int position) { + if (position == 0) { + act.getlorieView().requestFocus(); + } else { + final EditText editText = mTerminalToolbarViewPager.findViewById(R.id.terminal_toolbar_text_input); + if (editText != null) editText.requestFocus(); + } + } + + } + +} diff --git a/app/src/main/java/com/termux/x11/TermuxAppSharedProperties.java b/app/src/main/java/com/termux/x11/TermuxAppSharedProperties.java new file mode 100644 index 000000000..991698f6f --- /dev/null +++ b/app/src/main/java/com/termux/x11/TermuxAppSharedProperties.java @@ -0,0 +1,135 @@ +package com.termux.x11; + +import android.content.Context; +import android.net.Uri; + +import com.termux.shared.termux.extrakeys.ExtraKeysConstants; +import com.termux.shared.termux.extrakeys.ExtraKeysConstants.EXTRA_KEY_DISPLAY_MAPS; +import com.termux.shared.termux.extrakeys.ExtraKeysInfo; +import com.termux.shared.logger.Logger; +import com.termux.shared.termux.settings.properties.TermuxPropertyConstants; +import com.termux.shared.termux.settings.properties.TermuxSharedProperties; +import com.termux.shared.termux.TermuxConstants; + +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.io.File; +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import androidx.annotation.NonNull; + +public class TermuxAppSharedProperties extends TermuxSharedProperties { + + private ExtraKeysInfo mExtraKeysInfo; + + private static final String LOG_TAG = "TermuxAppSharedProperties"; + public static final String TERMUX_X11_APP_NAME = "Termux:X11"; + + private TermuxAppSharedProperties(@NonNull Context context, File propertiesFile) { + super(context, TERMUX_X11_APP_NAME, propertiesFile, + TermuxPropertyConstants.TERMUX_PROPERTIES_LIST, new SharedPropertiesParserClient()); + } + + public static TermuxAppSharedProperties build(Context context) { + Uri propertiesFileUri = Uri.parse("content://" + TermuxConstants.TERMUX_FILE_SHARE_URI_AUTHORITY + TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH); + File propertiesFile = new File(context.getFilesDir(), "termux.properties"); + runTermuxContentProviderReadCommand(context, propertiesFileUri, propertiesFile); + return new TermuxAppSharedProperties(context, propertiesFile); + } + + private static void runTermuxContentProviderReadCommand(Context context, Uri inputUri, File outFile) { + InputStream inputStream = null; + FileOutputStream fileOutputStream = null; + + try { + inputStream = context.getContentResolver().openInputStream(inputUri); + + if (!outFile.exists()) + outFile.createNewFile(); + fileOutputStream = new FileOutputStream(outFile); + byte[] buffer = new byte[4096]; + int readBytes; + while ((readBytes = inputStream.read(buffer)) > 0) { + // Log.d(LOG_TAG, "data: " + new String(buffer, 0, readBytes, Charset.defaultCharset())); + fileOutputStream.write(buffer, 0, readBytes); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (inputStream != null) + inputStream.close(); + if (fileOutputStream != null) + fileOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Reload the termux properties from disk into an in-memory cache. + */ + @Override + public void loadTermuxPropertiesFromDisk() { + super.loadTermuxPropertiesFromDisk(); + + setExtraKeys(); + } + + /** + * Set the terminal extra keys. + */ + private void setExtraKeys() { + mExtraKeysInfo = null; + + try { + // The mMap stores the extra key and style string values while loading properties + // Check {@link #getExtraKeysInternalPropertyValueFromValue(String)} and + // {@link #getExtraKeysStyleInternalPropertyValueFromValue(String)} + String extrakeys = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS, true); + String extraKeysStyle = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE, true); + + ExtraKeysConstants.ExtraKeyDisplayMap extraKeyDisplayMap = ExtraKeysInfo.getCharDisplayMapForStyle(extraKeysStyle); + if (EXTRA_KEY_DISPLAY_MAPS.DEFAULT_CHAR_DISPLAY.equals(extraKeyDisplayMap) && !TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE.equals(extraKeysStyle)) { + Logger.logError(TermuxSharedProperties.LOG_TAG, "The style \"" + extraKeysStyle + "\" for the key \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE + "\" is invalid. Using default style instead."); + extraKeysStyle = TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE; + } + + mExtraKeysInfo = new ExtraKeysInfo(extrakeys, extraKeysStyle, ExtraKeysConstants.CONTROL_CHARS_ALIASES); + } catch (JSONException e) { + Logger.showToast(mContext, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: " + e.toString(), true); + Logger.logStackTraceWithMessage(LOG_TAG, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: ", e); + + try { + mExtraKeysInfo = new ExtraKeysInfo(TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE, ExtraKeysConstants.CONTROL_CHARS_ALIASES); + } catch (JSONException e2) { + Logger.showToast(mContext, "Can't create default extra keys",true); + Logger.logStackTraceWithMessage(LOG_TAG, "Could create default extra keys: ", e); + mExtraKeysInfo = null; + } + } + } + + + + public ExtraKeysInfo getExtraKeysInfo() { + return mExtraKeysInfo; + } + + + + /** + * Load the {@link TermuxPropertyConstants#KEY_TERMINAL_TRANSCRIPT_ROWS} value from termux properties file on disk. + */ + public static int getTerminalTranscriptRows(Context context) { + return (int) TermuxSharedProperties.getInternalPropertyValue(context, TermuxPropertyConstants.getTermuxPropertiesFile(), + TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS, new SharedPropertiesParserClient()); + } + +} diff --git a/app/src/main/jni/lorie/backend/android/android-app.cpp b/app/src/main/jni/lorie/backend/android/android-app.cpp index 537af65ac..932591b2a 100644 --- a/app/src/main/jni/lorie/backend/android/android-app.cpp +++ b/app/src/main/jni/lorie/backend/android/android-app.cpp @@ -375,7 +375,7 @@ JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, unused jobject instance, if (shift || jshift) b->post([b]() { - b->keyboard_key(42, WL_KEYBOARD_KEY_STATE_RELEASED); // Send KEY_LEFTSHIFT + b->keyboard_key(42, WL_KEYBOARD_KEY_STATE_RELEASED); }); if (characters_ != nullptr) env->ReleaseStringUTFChars(characters_, characters); diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 029994187..90cf541a5 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -1,12 +1,13 @@ - + android:id="@+id/frame" + android:layout_above="@+id/terminal_toolbar_view_pager"> - + android:layout_height="37.5dp" + android:background="@android:drawable/screen_background_dark_transparent" + android:layout_alignParentBottom="true" /> - - - + diff --git a/app/src/main/res/layout/view_terminal_toolbar_extra_keys.xml b/app/src/main/res/layout/view_terminal_toolbar_extra_keys.xml new file mode 100644 index 000000000..2cafc9e43 --- /dev/null +++ b/app/src/main/res/layout/view_terminal_toolbar_extra_keys.xml @@ -0,0 +1,8 @@ + + diff --git a/app/src/main/res/layout/view_terminal_toolbar_text_input.xml b/app/src/main/res/layout/view_terminal_toolbar_text_input.xml new file mode 100644 index 000000000..86b3ce92d --- /dev/null +++ b/app/src/main/res/layout/view_terminal_toolbar_text_input.xml @@ -0,0 +1,16 @@ + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 827577a94..1c45ab756 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,12 +1,17 @@ - + - + android:summary="Show keyboard with additional keys. Will apply on app restart" + android:key="Toolbar" /> + + +