mirror of
https://github.com/xoureldeen/Vectras-VM-Android.git
synced 2026-04-28 14:30:03 +00:00
- 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.
1486 lines
66 KiB
Java
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();
|
|
}
|
|
}
|