Vectras-VM-Android/app/src/main/java/com/vectras/vm/x11/X11Activity.java
An Bui 2ecf30929a 3.9.0
- Fixed issues in Store categories.
- Fixed issue where custom memory was set too large.
- Improved ROM error detection.
- Updated the selection dialog.
- Added export and import support for paused virtual machines.
- Improved virtual machine resuming.
- Fixed crash when folders could not be opened.
- Improved virtual machine list data error detection.
- Fixed issue when selecting the Custom option in the Setup Wizard.
- Fixed crash when virtual machine pause fails.
2026-03-27 20:29:53 +07:00

1486 lines
66 KiB
Java

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();
}
}