package com.vectras.vm.x11; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import android.app.Activity; import android.app.Dialog; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; import android.content.pm.PackageManager; import android.os.Looper; import static android.view.InputDevice.KEYBOARD_TYPE_ALPHABETIC; import static android.view.KeyEvent.*; import static android.view.WindowManager.LayoutParams.*; import android.view.WindowManager; import android.widget.TextView; import androidx.activity.OnBackPressedCallback; import androidx.appcompat.content.res.AppCompatResources; import androidx.fragment.app.FragmentTransaction; import com.vectras.qemu.Config; import com.vectras.qemu.MainSettingsManager; import com.vectras.vm.Fragment.ControlersOptionsFragment; import com.vectras.vm.Fragment.LoggerDialogFragment; import com.vectras.vm.MainService; import com.vectras.vm.VMManager; import com.vectras.vm.main.core.MainStartVM; import com.vectras.vm.utils.DialogUtils; import com.vectras.vm.utils.UIUtils; import com.vectras.vm.widgets.JoystickView; import static com.vectras.vm.x11.CmdEntryPoint.ACTION_START; import static com.vectras.vm.x11.LoriePreferences.ACTION_PREFERENCES_CHANGED; import android.annotation.SuppressLint; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.view.DragEvent; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.math.MathUtils; import androidx.viewpager.widget.ViewPager; import com.vectras.vm.x11.input.InputEventSender; import com.vectras.vm.x11.input.InputStub; import com.vectras.vm.x11.input.TouchInputHandler.RenderStub; import com.vectras.vm.x11.input.TouchInputHandler; import com.vectras.vm.x11.utils.FullscreenWorkaround; import com.vectras.vm.x11.utils.KeyInterceptor; import com.vectras.vm.x11.utils.TermuxX11ExtraKeys; import com.vectras.vm.x11.utils.X11ToolbarViewPager; import com.vectras.vm.R; import com.vectras.vterm.Terminal; import java.util.Map; import java.util.Objects; @SuppressLint("ApplySharedPref") @SuppressWarnings({"deprecation", "unused"}) public class X11Activity extends AppCompatActivity implements View.OnApplyWindowInsetsListener { static final String ACTION_STOP = "com.vectras.vm.x11.ACTION_STOP"; public static Handler handler = new Handler(); FrameLayout frm; private TouchInputHandler mInputHandler; private ICmdEntryInterface service = null; public TermuxX11ExtraKeys mExtraKeys; static InputMethodManager inputMethodManager; private boolean mClientConnected = false; private View.OnKeyListener mLorieKeyListener; boolean captureVolumeKeys = false; private boolean filterOutWinKey = false; private boolean hideEKOnVolDown = false; private boolean toggleIMEUsingBackKey = false; private boolean useTermuxEKBarBehaviour = false; private static final int KEY_BACK = 158; public LinearLayout menuLayout; private static boolean oldFullscreen = false; private static boolean oldHideCutout = false; public TextView keyEventStatusTextView; public static LinearLayout desktop; public static LinearLayout gamepad; public boolean ctrlClicked = false; public boolean altClicked = false; private ImageButton qmpBtn; public static Activity activity; private final BroadcastReceiver receiver = new BroadcastReceiver() { @SuppressLint("UnspecifiedRegisterReceiverFlag") @Override public void onReceive(Context context, Intent intent) { if (ACTION_START.equals(intent.getAction())) { try { Log.v("LorieBroadcastReceiver", "Got new ACTION_START intent"); IBinder b = Objects.requireNonNull(intent.getBundleExtra("")).getBinder(""); service = ICmdEntryInterface.Stub.asInterface(b); Objects.requireNonNull(service) .asBinder() .linkToDeath( () -> { service = null; CmdEntryPoint.requestConnection(); Log.v("Lorie", "Disconnected"); runOnUiThread( () -> clientConnectedStateChanged(false)); }, 0); onReceiveConnection(); } catch (Exception e) { Log.e( "X11Activity", "Something went wrong while we extracted connection details from binder.", e); } } else if (ACTION_STOP.equals(intent.getAction())) { finishAffinity(); } else if (ACTION_PREFERENCES_CHANGED.equals(intent.getAction())) { Log.d("X11Activity", "preference: " + intent.getStringExtra("key")); if (!"additionalKbdVisible".equals(intent.getStringExtra("key"))) onPreferencesChanged(""); } } }; @SuppressLint("StaticFieldLeak") private static X11Activity instance; public X11Activity() { instance = this; } public static X11Activity getInstance() { return instance; } @Override @SuppressLint({ "AppCompatMethod", "ObsoleteSdkInt", "ClickableViewAccessibility", "WrongConstant", "UnspecifiedRegisterReceiverFlag" }) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); UIUtils.fullScreen(this); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); int modeValue = Integer.parseInt(preferences.getString("touchMode", "1")) - 1; if (modeValue > 2) { SharedPreferences.Editor e = Objects.requireNonNull(preferences).edit(); e.putString("touchMode", "1"); e.apply(); } oldFullscreen = preferences.getBoolean("fullscreen", true); oldHideCutout = preferences.getBoolean("hideCutout", true); preferences.registerOnSharedPreferenceChangeListener( (sharedPreferences, key) -> onPreferencesChanged(key)); getWindow() .setFlags( FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS | FLAG_KEEP_SCREEN_ON | FLAG_TRANSLUCENT_STATUS, 0); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_x11); activity = this; frm = findViewById(R.id.frame); findViewById(R.id.preferences_button) .setOnClickListener( (l) -> startActivity( new Intent(this, LoriePreferences.class) { { setAction(Intent.ACTION_MAIN); } })); findViewById(R.id.help_button) .setOnClickListener( (l) -> startActivity( new Intent( Intent.ACTION_VIEW, Uri.parse( "https://github.com/termux/termux-x11/blob/master/README.md#running-graphical-applications")))); LorieView lorieView = findViewById(R.id.lorieView); View lorieParent = (View) lorieView.getParent(); mInputHandler = new TouchInputHandler( this, new RenderStub.NullStub() { @Override public void swipeDown() { toggleExtraKeys(); } }, new InputEventSender(lorieView)); mLorieKeyListener = (v, k, e) -> { InputDevice dev = e.getDevice(); boolean result; if (!captureVolumeKeys && (k == KEYCODE_VOLUME_DOWN || k == KEYCODE_VOLUME_UP)) return false; if (hideEKOnVolDown && k == KEYCODE_VOLUME_DOWN) { if (e.getAction() == ACTION_UP) toggleExtraKeys(); return true; } if (k == KEYCODE_BACK) { if (e.isFromSource(InputDevice.SOURCE_MOUSE) || e.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { if (e.getRepeatCount() != 0) // ignore auto-repeat return true; if (e.getAction() == ACTION_UP || e.getAction() == ACTION_DOWN) lorieView.sendMouseEvent( -1, -1, InputStub.BUTTON_RIGHT, e.getAction() == ACTION_DOWN, true); return true; } if (e.getScanCode() == KEY_BACK && e.getDevice().getKeyboardType() != KEYBOARD_TYPE_ALPHABETIC || e.getScanCode() == 0) { if (toggleIMEUsingBackKey && e.getAction() == ACTION_UP) { activity.finish(); } return true; } } result = mInputHandler.sendKeyEvent(e); // Do not steal dedicated buttons from a full external keyboard. if (useTermuxEKBarBehaviour && mExtraKeys != null && (dev == null || dev.isVirtual())) mExtraKeys.unsetSpecialKeys(); return result; }; lorieParent.setOnTouchListener( (v, e) -> mInputHandler.handleTouchEvent(lorieParent, lorieView, e)); lorieParent.setOnHoverListener( (v, e) -> mInputHandler.handleTouchEvent(lorieParent, lorieView, e)); lorieParent.setOnGenericMotionListener( (v, e) -> mInputHandler.handleTouchEvent(lorieParent, lorieView, e)); if (SDK_INT >= VERSION_CODES.O) { lorieView.setOnCapturedPointerListener( (v, e) -> mInputHandler.handleTouchEvent(lorieView, lorieView, e)); } if (SDK_INT >= VERSION_CODES.O) { lorieParent.setOnCapturedPointerListener( (v, e) -> mInputHandler.handleTouchEvent(lorieView, lorieView, e)); } lorieView.setOnKeyListener(mLorieKeyListener); lorieView.setCallback( (sfc, surfaceWidth, surfaceHeight, screenWidth, screenHeight) -> { int framerate = (int) ((lorieView.getDisplay() != null) ? lorieView.getDisplay().getRefreshRate() : 30); mInputHandler.handleHostSizeChanged(surfaceWidth, surfaceHeight); mInputHandler.handleClientSizeChanged(screenWidth, screenHeight); LorieView.sendWindowChange(screenWidth, screenHeight, framerate); if (service != null) { try { service.windowChanged( sfc, lorieView.getDisplay() != null ? lorieView.getDisplay().getName() : "screen"); } catch (RemoteException e) { Log.e("X11Activity", "failed to send windowChanged request", e); } } }); if (SDK_INT >= VERSION_CODES.O) { registerReceiver( receiver, new IntentFilter(ACTION_START) { { addAction(ACTION_PREFERENCES_CHANGED); addAction(ACTION_STOP); } }, SDK_INT >= VERSION_CODES.TIRAMISU ? RECEIVER_EXPORTED : 0); } inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // 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); CmdEntryPoint.requestConnection(); onPreferencesChanged(""); toggleExtraKeys(false, false); checkXEvents(); initStylusAuxButtons(); initMouseAuxButtons(); desktop = findViewById(R.id.desktop); gamepad = findViewById(R.id.gamepad); if (Objects.equals(MainSettingsManager.getControlMode(activity), "D")) { desktop.setVisibility(View.VISIBLE); gamepad.setVisibility(View.GONE); } else if (Objects.equals(MainSettingsManager.getControlMode(activity), "G")) { desktop.setVisibility(View.GONE); gamepad.setVisibility(View.VISIBLE); } else if (Objects.equals(MainSettingsManager.getControlMode(activity), "H")) { desktop.setVisibility(View.GONE); gamepad.setVisibility(View.GONE); } ImageButton shutdownBtn = findViewById(R.id.shutdownBtn); ImageButton settingBtn = findViewById(R.id.btnSettings); ImageButton btnFit = findViewById(R.id.btnFit); ImageButton keyboardBtn = findViewById(R.id.kbdBtn); ImageButton controllersBtn = findViewById(R.id.btnMode); ImageButton upBtn = findViewById(R.id.upBtn); ImageButton leftBtn = findViewById(R.id.leftBtn); ImageButton downBtn = findViewById(R.id.downBtn); ImageButton rightBtn = findViewById(R.id.rightBtn); ImageButton enterBtn = findViewById(R.id.enterBtn); ImageButton escBtn = findViewById(R.id.escBtn); ImageButton ctrlBtn = findViewById(R.id.ctrlBtn); ImageButton altBtn = findViewById(R.id.altBtn); ImageButton delBtn = findViewById(R.id.delBtn); ImageButton btnVterm = findViewById(R.id.btnVterm); Button eBtn = findViewById(R.id.eBtn); Button rBtn = findViewById(R.id.rBtn); Button qBtn = findViewById(R.id.qBtn); Button xBtn = findViewById(R.id.xBtn); ImageButton ctrlGameBtn = findViewById(R.id.ctrlGameBtn); Button spaceBtn = findViewById(R.id.spaceBtn); Button tabGameBtn = findViewById(R.id.tabGameBtn); Button tabBtn = findViewById(R.id.tabBtn); ImageButton upGameBtn = findViewById(R.id.upGameBtn); ImageButton downGameBtn = findViewById(R.id.downGameBtn); ImageButton leftGameBtn = findViewById(R.id.leftGameBtn); ImageButton rightGameBtn = findViewById(R.id.rightGameBtn); ImageButton enterGameBtn = findViewById(R.id.enterGameBtn); ImageButton appsBtn = findViewById(R.id.btnPrograms); qmpBtn = findViewById(R.id.btnQmp); final boolean[] isFullScreen = {false}; btnFit.setOnClickListener(view -> { sendKey(KEYCODE_CTRL_LEFT, false); sendKey(KEYCODE_ALT_LEFT, false); sendKey(KEYCODE_F, false); sendKey(KEYCODE_CTRL_LEFT, true); sendKey(KEYCODE_ALT_LEFT, true); sendKey(KEYCODE_F, true); if (isFullScreen[0]) { btnFit.setImageDrawable(AppCompatResources.getDrawable(X11Activity.this, R.drawable.close_fullscreen_24px)); isFullScreen[0] = false; } else { btnFit.setImageDrawable(AppCompatResources.getDrawable(X11Activity.this, R.drawable.open_in_full_24px)); isFullScreen[0] = true; } }); appsBtn.setOnClickListener(v -> { Dialog dialog = new Dialog(activity); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setContentView(R.layout.dialog_programs); Objects.requireNonNull(dialog.getWindow()).setBackgroundDrawableResource(android.R.color.transparent); WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes(); layoutParams.alpha = 1f; dialog.getWindow().setAttributes(layoutParams); ImageButton termBtn = dialog.findViewById(R.id.btnTerminal); ImageButton vkCubeBtn = dialog.findViewById(R.id.btnVkCube); ImageButton glxGearsBtn = dialog.findViewById(R.id.btnGlxGears); termBtn.setOnClickListener(v1 -> { new Terminal(activity).executeShellCommand2("xfce4-terminal", false, activity); dialog.dismiss(); }); glxGearsBtn.setOnClickListener(v1 -> { new Terminal(activity).executeShellCommand2("glxgears", false, activity); dialog.dismiss(); }); vkCubeBtn.setOnClickListener(v1 -> { new Terminal(activity).executeShellCommand2("vkcube", false, activity); dialog.dismiss(); }); try { dialog.show(); } catch (WindowManager.BadTokenException e) { e.printStackTrace(); } }); upGameBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_UP, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_UP, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); leftGameBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_LEFT, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_LEFT, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); downGameBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_DOWN, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_DOWN, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); rightGameBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_RIGHT, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_RIGHT, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); JoystickView joystick = findViewById(R.id.joyStick); joystick.setVisibility(View.GONE); tabBtn.setOnClickListener(v -> keyDownUp(KEYCODE_TAB)); tabGameBtn.setOnClickListener(v -> keyDownUp(KEYCODE_TAB)); enterGameBtn.setOnClickListener(v -> keyDownUp(KEYCODE_ENTER)); eBtn.setOnClickListener(v -> keyDownUp(KEYCODE_E)); rBtn.setOnClickListener(v -> keyDownUp(KEYCODE_R)); qBtn.setOnClickListener(v -> keyDownUp(KEYCODE_Q)); xBtn.setOnClickListener(v -> keyDownUp(KEYCODE_X)); ctrlGameBtn.setOnClickListener(v -> keyDownUp(KEYCODE_CTRL_LEFT)); spaceBtn.setOnClickListener(v -> keyDownUp(KEYCODE_SPACE)); btnVterm.setOnClickListener(v -> { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); // Create and show the dialog. LoggerDialogFragment newFragment = new LoggerDialogFragment(); newFragment.show(ft, "Logger"); }); shutdownBtn.setOnClickListener(v -> finish()); keyboardBtn.setOnClickListener(v -> new Handler(Looper.getMainLooper()).postDelayed(() -> toggleKeyboardVisibility(X11Activity.this), 200)); controllersBtn.setOnClickListener(v -> { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); // Create and show the dialog. ControlersOptionsFragment newFragment = new ControlersOptionsFragment(); newFragment.show(ft, "Controllers"); }); findViewById(R.id.btnSettings) .setOnClickListener( (l) -> startActivity( new Intent(this, LoriePreferences.class) { { setAction(Intent.ACTION_MAIN); } })); upBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_UP, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_UP, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); leftBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_LEFT, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_LEFT, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); downBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_DOWN, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_DOWN, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); rightBtn.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { sendKey(KEYCODE_DPAD_RIGHT, false); v.animate().scaleXBy(-0.2f).setDuration(200).start(); v.animate().scaleYBy(-0.2f).setDuration(200).start(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { sendKey(KEYCODE_DPAD_RIGHT, true); v.animate().cancel(); v.animate().scaleX(1f).setDuration(200).start(); v.animate().scaleY(1f).setDuration(200).start(); return true; } return false; }); escBtn.setOnClickListener(v -> keyDownUp(KEYCODE_ESCAPE)); enterBtn.setOnClickListener(v -> keyDownUp(KEYCODE_ENTER)); ctrlBtn.setOnClickListener(new View.OnClickListener() { @SuppressLint("UseCompatLoadingForDrawables") @Override public void onClick(View v) { if (!ctrlClicked) { sendKey(KEYCODE_CTRL_LEFT, false); ctrlBtn.setBackground(getResources().getDrawable(R.drawable.controls_button2)); ctrlClicked = true; } else { sendKey(KEYCODE_CTRL_LEFT, true); ctrlBtn.setBackground(getResources().getDrawable(R.drawable.controls_button1)); ctrlClicked = false; } } }); altBtn.setOnClickListener(new View.OnClickListener() { @SuppressLint("UseCompatLoadingForDrawables") @Override public void onClick(View v) { if (!altClicked) { sendKey(KEYCODE_ALT_LEFT, false); altBtn.setBackground(getResources().getDrawable(R.drawable.controls_button2)); altClicked = true; } else { sendKey(KEYCODE_ALT_LEFT, true); altBtn.setBackground(getResources().getDrawable(R.drawable.controls_button1)); altClicked = false; } } }); delBtn.setOnClickListener(v -> keyDownUp(KEYCODE_DEL)); qmpBtn.setVisibility(View.GONE); qmpBtn.setOnClickListener(v -> { // if (monitorMode) { // onVNC(); // qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_terminal_24)); // } else { // onMonitor(); // qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_computer_24)); // } }); Button rightClickBtn = findViewById(R.id.rightClickBtn); Button middleClickBtn = findViewById(R.id.middleBtn); Button leftClickBtn = findViewById(R.id.leftClickBtn); ImageButton winBtn = findViewById(R.id.winBtn); if (SDK_INT >= VERSION_CODES.N) { Map.of( leftClickBtn, InputStub.BUTTON_LEFT, middleClickBtn, InputStub.BUTTON_MIDDLE, rightClickBtn, InputStub.BUTTON_RIGHT) .forEach( (v, b) -> v.setOnTouchListener( (__, e) -> { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: getLorieView() .sendMouseEvent(0, 0, b, true, true); v.setPressed(true); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: getLorieView() .sendMouseEvent(0, 0, b, false, true); v.setPressed(false); break; } return true; })); } else { leftClickBtn.setVisibility(View.GONE); middleClickBtn.setVisibility(View.GONE); rightClickBtn.setVisibility(View.GONE); } winBtn.setOnClickListener(v -> { sendKey(KEYCODE_CTRL_LEFT, false); sendKey(KEYCODE_ESCAPE, false); sendKey(KEYCODE_CTRL_LEFT, false); sendKey(KEYCODE_ESCAPE, false); }); getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { if (findViewById(R.id.mainControl).getVisibility() == View.GONE) { findViewById(R.id.mainControl).setVisibility(View.VISIBLE); } else if (findViewById(R.id.mainControl).getVisibility() == View.VISIBLE) { findViewById(R.id.mainControl).setVisibility(View.GONE); } } }); MainStartVM.startPending(this); } private void keyDownUp(int keyEventCode) { dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); } private void sendKey(int keyEventCode, boolean up) { if (up) dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); else dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); } @Override protected void onDestroy() { unregisterReceiver(receiver); super.onDestroy(); } // Register the needed events to handle stylus as left, middle and right click @SuppressLint("ClickableViewAccessibility") private void initStylusAuxButtons() { SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this); boolean stylusMenuEnabled = p.getBoolean("showStylusClickOverride", false); final float menuUnselectedTrasparency = 0.66f; final float menuSelectedTrasparency = 1.0f; Button left = findViewById(R.id.button_left_click); Button right = findViewById(R.id.button_right_click); Button middle = findViewById(R.id.button_middle_click); Button visibility = findViewById(R.id.button_visibility); LinearLayout overlay = findViewById(R.id.mouse_helper_visibility); LinearLayout buttons = findViewById(R.id.mouse_helper_secondary_layer); overlay.setOnTouchListener((v, e) -> true); overlay.setOnHoverListener((v, e) -> true); overlay.setOnGenericMotionListener((v, e) -> true); if (SDK_INT >= VERSION_CODES.O) { overlay.setOnCapturedPointerListener((v, e) -> true); } overlay.setVisibility(stylusMenuEnabled ? View.VISIBLE : View.GONE); View.OnClickListener listener = view -> { TouchInputHandler.STYLUS_INPUT_HELPER_MODE = (view.equals(left) ? 1 : (view.equals(middle) ? 2 : (view.equals(right) ? 3 : 0))); left.setAlpha( (TouchInputHandler.STYLUS_INPUT_HELPER_MODE == 1) ? menuSelectedTrasparency : menuUnselectedTrasparency); middle.setAlpha( (TouchInputHandler.STYLUS_INPUT_HELPER_MODE == 2) ? menuSelectedTrasparency : menuUnselectedTrasparency); right.setAlpha( (TouchInputHandler.STYLUS_INPUT_HELPER_MODE == 3) ? menuSelectedTrasparency : menuUnselectedTrasparency); visibility.setAlpha(menuUnselectedTrasparency); }; left.setOnClickListener(listener); middle.setOnClickListener(listener); right.setOnClickListener(listener); visibility.setOnClickListener( view -> { if (buttons.getVisibility() == View.VISIBLE) { buttons.setVisibility(View.GONE); visibility.setAlpha(menuUnselectedTrasparency); int m = TouchInputHandler.STYLUS_INPUT_HELPER_MODE; visibility.setText(m == 1 ? "L" : (m == 2 ? "M" : (m == 3 ? "R" : "U"))); } else { buttons.setVisibility(View.VISIBLE); visibility.setAlpha(menuUnselectedTrasparency); visibility.setText("X"); // Calculate screen border making sure btn is fully inside the view float maxX = frm.getWidth() - 4 * left.getWidth(); float maxY = frm.getHeight() - 4 * left.getHeight(); // Make sure the Stylus menu is fully inside the screen overlay.setX(MathUtils.clamp(overlay.getX(), 0, maxX)); overlay.setY(MathUtils.clamp(overlay.getY(), 0, maxY)); int m = TouchInputHandler.STYLUS_INPUT_HELPER_MODE; listener.onClick( m == 1 ? left : (m == 2 ? middle : (m == 3 ? right : left))); } }); // Simulated mouse click 1 = left , 2 = middle , 3 = right TouchInputHandler.STYLUS_INPUT_HELPER_MODE = 1; listener.onClick(left); visibility.setOnLongClickListener( v -> { if (SDK_INT >= VERSION_CODES.N) { v.startDragAndDrop( ClipData.newPlainText("", ""), new View.DragShadowBuilder(visibility) { public void onDrawShadow(@NonNull Canvas canvas) {} }, null, View.DRAG_FLAG_GLOBAL); } frm.setOnDragListener( (v2, event) -> { // Calculate screen border making sure btn is fully inside the view float maxX = frm.getWidth() - visibility.getWidth(); float maxY = frm.getHeight() - visibility.getHeight(); switch (event.getAction()) { case DragEvent.ACTION_DRAG_LOCATION: // Center touch location with btn icon float dX = event.getX() - visibility.getWidth() / 2.0f; float dY = event.getY() - visibility.getHeight() / 2.0f; // Make sure the dragged btn is inside the view with clamp overlay.setX(MathUtils.clamp(dX, 0, maxX)); overlay.setY(MathUtils.clamp(dY, 0, maxY)); break; case DragEvent.ACTION_DRAG_ENDED: // Make sure the dragged btn is inside the view overlay.setX(MathUtils.clamp(overlay.getX(), 0, maxX)); overlay.setY(MathUtils.clamp(overlay.getY(), 0, maxY)); break; } return true; }); return true; }); } void setSize(View v, int width, int height) { ViewGroup.LayoutParams p = v.getLayoutParams(); p.width = (int) (width * getResources().getDisplayMetrics().density); p.height = (int) (height * getResources().getDisplayMetrics().density); v.setLayoutParams(p); v.setMinimumWidth((int) (width * getResources().getDisplayMetrics().density)); v.setMinimumHeight((int) (height * getResources().getDisplayMetrics().density)); } @SuppressLint("ClickableViewAccessibility") void initMouseAuxButtons() { Button left = findViewById(R.id.mouse_button_left_click); Button right = findViewById(R.id.mouse_button_right_click); Button middle = findViewById(R.id.mouse_button_middle_click); ImageButton pos = findViewById(R.id.mouse_buttons_position); LinearLayout primaryLayer = findViewById(R.id.mouse_buttons); LinearLayout secondaryLayer = findViewById(R.id.mouse_buttons_secondary_layer); SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this); boolean mouseHelperEnabled = p.getBoolean("showMouseHelper", false) && "1".equals(p.getString("touchMode", "1")); primaryLayer.setVisibility(mouseHelperEnabled ? View.VISIBLE : View.GONE); pos.setOnClickListener( (v) -> { if (secondaryLayer.getOrientation() == LinearLayout.HORIZONTAL) { setSize(left, 48, 96); setSize(right, 48, 96); secondaryLayer.setOrientation(LinearLayout.VERTICAL); } else { setSize(left, 96, 48); setSize(right, 96, 48); secondaryLayer.setOrientation(LinearLayout.HORIZONTAL); } handler.postDelayed( () -> { int[] offset = new int[2]; frm.getLocationOnScreen(offset); primaryLayer.setX( MathUtils.clamp( primaryLayer.getX(), offset[0], offset[0] + frm.getWidth() - primaryLayer.getWidth())); primaryLayer.setY( MathUtils.clamp( primaryLayer.getY(), offset[1], offset[1] + frm.getHeight() - primaryLayer.getHeight())); }, 10); }); if (SDK_INT >= VERSION_CODES.N) { Map.of( left, InputStub.BUTTON_LEFT, middle, InputStub.BUTTON_MIDDLE, right, InputStub.BUTTON_RIGHT) .forEach( (v, b) -> v.setOnTouchListener( (__, e) -> { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: getLorieView() .sendMouseEvent(0, 0, b, true, true); v.setPressed(true); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: getLorieView() .sendMouseEvent(0, 0, b, false, true); v.setPressed(false); break; } return true; })); } pos.setOnTouchListener( new View.OnTouchListener() { final int touchSlop = (int) Math.pow( ViewConfiguration.get(X11Activity.this) .getScaledTouchSlop(), 2); final int tapTimeout = ViewConfiguration.getTapTimeout(); final float[] startOffset = new float[2]; final int[] startPosition = new int[2]; long startTime; @Override public boolean onTouch(View v, MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: primaryLayer.getLocationOnScreen(startPosition); startOffset[0] = e.getX(); startOffset[1] = e.getY(); startTime = SystemClock.uptimeMillis(); pos.setPressed(true); break; case MotionEvent.ACTION_MOVE: { int[] offset = new int[2]; int[] offset2 = new int[2]; primaryLayer.getLocationOnScreen(offset); frm.getLocationOnScreen(offset2); primaryLayer.setX( MathUtils.clamp( offset[0] - startOffset[0] + e.getX(), offset2[0], offset2[0] + frm.getWidth() - primaryLayer.getWidth())); primaryLayer.setY( MathUtils.clamp( offset[1] - startOffset[1] + e.getY(), offset2[1], offset2[1] + frm.getHeight() - primaryLayer.getHeight())); break; } case MotionEvent.ACTION_UP: { final int[] _pos = new int[2]; primaryLayer.getLocationOnScreen(_pos); int deltaX = (int) (startOffset[0] - e.getX()) + (startPosition[0] - _pos[0]); int deltaY = (int) (startOffset[1] - e.getY()) + (startPosition[1] - _pos[1]); pos.setPressed(false); if (deltaX * deltaX + deltaY * deltaY < touchSlop && SystemClock.uptimeMillis() - startTime <= tapTimeout) { v.performClick(); return true; } break; } } return true; } }); } void onReceiveConnection() { try { if (service != null && service.asBinder().isBinderAlive()) { Log.v("LorieBroadcastReceiver", "Extracting logcat fd."); ParcelFileDescriptor logcatOutput = service.getLogcatOutput(); if (logcatOutput != null) LorieView.startLogcat(logcatOutput.detachFd()); tryConnect(); } } catch (Exception e) { Log.e("X11Activity", "Something went wrong while we were establishing connection", e); } } void tryConnect() { if (mClientConnected) return; try { ParcelFileDescriptor fd = service == null ? null : service.getXConnection(); if (fd != null) { Log.v("X11Activity", "Extracting X connection socket."); LorieView.connect(fd.detachFd()); getLorieView().triggerCallback(); clientConnectedStateChanged(true); getLorieView() .reloadPreferences(PreferenceManager.getDefaultSharedPreferences(this)); } else handler.postDelayed(this::tryConnect, 500); } catch (Exception e) { Log.e("X11Activity", "Something went wrong while we were establishing connection", e); service = null; // We should reset the View for the case if we have sent it's surface to the client. getLorieView().regenerate(); } } void onPreferencesChanged(String key) { if ("additionalKbdVisible".equals(key)) return; SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this); LorieView lorieView = getLorieView(); mInputHandler.reloadPreferences(p); lorieView.reloadPreferences(p); captureVolumeKeys = p.getBoolean("captureVolumeKeys", true); setTerminalToolbarView(); onWindowFocusChanged(true); lorieView.triggerCallback(); filterOutWinKey = p.getBoolean("filterOutWinkey", false); if (p.getBoolean("enableAccessibilityServiceAutomatically", false)) { try { Settings.Secure.putString( getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "com.vectras.vm.x11/.utils.KeyInterceptor"); Settings.Secure.putString( getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, "1"); } catch (SecurityException e) { new AlertDialog.Builder(this) .setTitle("Permission denied") .setMessage( """ Android requires WRITE_SECURE_SETTINGS permission to start accessibility service automatically. Please, launch this command using ADB: adb shell pm grant com.vectras.vm.x11 android.permission.WRITE_SECURE_SETTINGS""") .setNegativeButton("OK", null) .create() .show(); SharedPreferences.Editor edit = p.edit(); edit.putBoolean("enableAccessibilityServiceAutomatically", false); edit.commit(); } } else if (checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) KeyInterceptor.shutdown(); hideEKOnVolDown = p.getBoolean("hideEKOnVolDown", false); useTermuxEKBarBehaviour = p.getBoolean("useTermuxEKBarBehaviour", false); toggleIMEUsingBackKey = p.getBoolean("toggleIMEUsingBackKey", true); int requestedOrientation = p.getBoolean("forceLandscape", true) ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; if (getRequestedOrientation() != requestedOrientation) setRequestedOrientation(requestedOrientation); findViewById(R.id.mouse_buttons) .setVisibility( p.getBoolean("showMouseHelper", false) && "1".equals(p.getString("touchMode", "1")) && mClientConnected ? View.VISIBLE : View.GONE); LinearLayout buttons = findViewById(R.id.mouse_helper_visibility); if (p.getBoolean("showStylusClickOverride", false)) { buttons.setVisibility(View.VISIBLE); } else { // Reset default input back to normal TouchInputHandler.STYLUS_INPUT_HELPER_MODE = 1; final float menuUnselectedTrasparency = 0.66f; final float menuSelectedTrasparency = 1.0f; findViewById(R.id.button_left_click).setAlpha(menuSelectedTrasparency); findViewById(R.id.button_right_click).setAlpha(menuUnselectedTrasparency); findViewById(R.id.button_middle_click).setAlpha(menuUnselectedTrasparency); findViewById(R.id.button_visibility).setAlpha(menuUnselectedTrasparency); buttons.setVisibility(View.GONE); } getTerminalToolbarViewPager().setAlpha(((float) p.getInt("opacityEKBar", 100)) / 100); lorieView.requestLayout(); lorieView.invalidate(); } @Override public void onResume() { super.onResume(); setTerminalToolbarView(); getLorieView().requestFocus(); } @Override public void onPause() { View view = getCurrentFocus(); if (view == null) { view = getLorieView(); view.requestFocus(); } inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); super.onPause(); } public LorieView getLorieView() { return findViewById(R.id.lorieView); } public ViewPager getTerminalToolbarViewPager() { return findViewById(R.id.terminal_toolbar_view_pager); } private void setTerminalToolbarView() { final ViewPager terminalToolbarViewPager = getTerminalToolbarViewPager(); terminalToolbarViewPager.setAdapter( new X11ToolbarViewPager.PageAdapter( this, (v, k, e) -> mInputHandler.sendKeyEvent(e))); terminalToolbarViewPager.addOnPageChangeListener( new X11ToolbarViewPager.OnPageChangeListener(this, terminalToolbarViewPager)); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); boolean enabled = preferences.getBoolean("showAdditionalKbd", false); boolean showNow = enabled && preferences.getBoolean("additionalKbdVisible", true); terminalToolbarViewPager.setVisibility(showNow ? View.VISIBLE : View.GONE); findViewById(R.id.terminal_toolbar_view_pager).requestFocus(); handler.postDelayed( () -> { if (mExtraKeys != null) { ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams(); layoutParams.height = Math.round( 37.5f * getResources().getDisplayMetrics().density * (mExtraKeys.getExtraKeysInfo() == null ? 0 : mExtraKeys .getExtraKeysInfo() .getMatrix() .length)); terminalToolbarViewPager.setLayoutParams(layoutParams); } frm.setPadding( 0, 0, 0, preferences.getBoolean("adjustHeightForEK", false) && terminalToolbarViewPager.getVisibility() == View.VISIBLE ? terminalToolbarViewPager.getHeight() : 0); }, 200); } public void toggleExtraKeys(boolean visible, boolean saveState) { runOnUiThread( () -> { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); boolean enabled = preferences.getBoolean("showAdditionalKbd", false); ViewPager pager = getTerminalToolbarViewPager(); ViewGroup parent = (ViewGroup) pager.getParent(); boolean show = enabled && mClientConnected && visible; if (show) { setTerminalToolbarView(); getTerminalToolbarViewPager().bringToFront(); } else { parent.removeView(pager); parent.addView(pager, 0); if (mExtraKeys != null) mExtraKeys.unsetSpecialKeys(); } frm.setPadding( 0, 0, 0, preferences.getBoolean("adjustHeightForEK", false) && show ? pager.getHeight() : 0); if (enabled && saveState) { SharedPreferences.Editor edit = preferences.edit(); edit.putBoolean("additionalKbdVisible", show); edit.commit(); } pager.setVisibility(show ? View.VISIBLE : View.INVISIBLE); getLorieView().requestFocus(); }); } public void toggleExtraKeys() { int visibility = getTerminalToolbarViewPager().getVisibility(); toggleExtraKeys(visibility != View.VISIBLE, true); getLorieView().requestFocus(); } public boolean handleKey(KeyEvent e) { if (filterOutWinKey && (e.getKeyCode() == KEYCODE_META_LEFT || e.getKeyCode() == KEYCODE_META_RIGHT || e.isMetaPressed())) return false; return mLorieKeyListener.onKey(getLorieView(), e.getKeyCode(), e); } int orientation; @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation != orientation) { View view = getCurrentFocus(); if (view == null) { view = getLorieView(); view.requestFocus(); } inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } orientation = newConfig.orientation; setTerminalToolbarView(); } @SuppressLint("WrongConstant") @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this); Window window = getWindow(); View decorView = window.getDecorView(); boolean fullscreen = p.getBoolean("fullscreen", true); boolean hideCutout = p.getBoolean("hideCutout", true); boolean reseed = p.getBoolean("Reseed", true); if (oldHideCutout != hideCutout || oldFullscreen != fullscreen) { oldHideCutout = hideCutout; oldFullscreen = fullscreen; // For some reason cutout or fullscreen change makes layout calculations wrong and // invalid. // I did not find simple and reliable way to fix it so it is better to start from the // beginning. recreate(); return; } int requestedOrientation = p.getBoolean("forceLandscape", true) ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; if (getRequestedOrientation() != requestedOrientation) setRequestedOrientation(requestedOrientation); if (hasFocus) { if (SDK_INT >= VERSION_CODES.P) { if (hideCutout) getWindow().getAttributes().layoutInDisplayCutoutMode = (SDK_INT >= VERSION_CODES.R) ? LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS : LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; else getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; } window.setStatusBarColor(Color.BLACK); window.setNavigationBarColor(Color.BLACK); } window.setFlags( FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS | FLAG_KEEP_SCREEN_ON | FLAG_TRANSLUCENT_STATUS, 0); if (hasFocus) { if (fullscreen) { window.addFlags(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(FLAG_FULLSCREEN); decorView.setSystemUiVisibility(0); } } if (p.getBoolean("keepScreenOn", true)) window.addFlags(FLAG_KEEP_SCREEN_ON); else window.clearFlags(FLAG_KEEP_SCREEN_ON); window.setSoftInputMode( (reseed ? SOFT_INPUT_ADJUST_RESIZE : SOFT_INPUT_ADJUST_PAN) | SOFT_INPUT_STATE_HIDDEN); ((FrameLayout) findViewById(android.R.id.content)) .getChildAt(0) .setFitsSystemWindows(!fullscreen); } public static boolean hasPipPermission(@NonNull Context context) { AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); if (appOpsManager == null) return false; else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return appOpsManager.unsafeCheckOpNoThrow( AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()) == AppOpsManager.MODE_ALLOWED; else return appOpsManager.checkOpNoThrow( AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()) == AppOpsManager.MODE_ALLOWED; } @Override public void onUserLeaveHint() { super.onUserLeaveHint(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); if (preferences.getBoolean("PIP", false) && hasPipPermission(this)) { if (SDK_INT >= VERSION_CODES.N && getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { enterPictureInPictureMode(); } } } @Override public void onPictureInPictureModeChanged( boolean isInPictureInPictureMode, @NonNull Configuration newConfig) { toggleExtraKeys(!isInPictureInPictureMode, false); super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); } /** * @noinspection NullableProblems */ @SuppressLint("WrongConstant") @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { handler.postDelayed(() -> getLorieView().triggerCallback(), 100); return insets; } /** * Manually toggle soft keyboard visibility * * @param context calling context */ public static void toggleKeyboardVisibility(Context context) { Log.d("X11Activity", "Toggling keyboard visibility"); if (inputMethodManager != null) inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } @SuppressWarnings("SameParameterValue") void clientConnectedStateChanged(boolean connected) { runOnUiThread( () -> { SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this); mClientConnected = connected; toggleExtraKeys(connected && p.getBoolean("additionalKbdVisible", true), true); findViewById(R.id.mouse_buttons) .setVisibility( p.getBoolean("showMouseHelper", false) && "1".equals(p.getString("touchMode", "1")) && mClientConnected ? View.VISIBLE : View.GONE); findViewById(R.id.stub) .setVisibility(connected ? View.INVISIBLE : View.VISIBLE); getLorieView().setVisibility(connected ? View.VISIBLE : View.INVISIBLE); getLorieView().regenerate(); // We should recover connection in the case if file descriptor for some reason // was broken... if (!connected) tryConnect(); if (connected) if (SDK_INT >= VERSION_CODES.N) { getLorieView() .setPointerIcon( PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); } }); } private void checkXEvents() { getLorieView().handleXEvents(); handler.postDelayed(this::checkXEvents, 300); } public static void getRealMetrics(DisplayMetrics m) { if (getInstance() != null && getInstance().getLorieView() != null && getInstance().getLorieView().getDisplay() != null) getInstance().getLorieView().getDisplay().getRealMetrics(m); } public static void setCapturingEnabled(boolean enabled) { if (getInstance() == null || getInstance().mInputHandler == null) return; getInstance().mInputHandler.setCapturingEnabled(enabled); } public boolean shouldInterceptKeys() { View textInput = findViewById(R.id.terminal_toolbar_text_input); if (mInputHandler == null || !hasWindowFocus() || (textInput != null && textInput.isFocused())) return false; return mInputHandler.shouldInterceptKeys(); } private void shutdownthisvm() { VMManager.shutdownCurrentVM(); Config.setDefault(); MainService.stopService(); finish(); } }