package com.vectras.qemu; import android.annotation.SuppressLint; import android.app.Activity; import androidx.appcompat.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.drawable.ColorDrawable; import android.media.AudioManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Vibrator; import android.util.Log; import android.view.Display; import android.view.GestureDetector; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import androidx.appcompat.app.ActionBar; import androidx.core.view.GravityCompat; import androidx.core.view.MenuItemCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.FragmentTransaction; import androidx.recyclerview.widget.RecyclerView; import com.vectras.qemu.utils.FileUtils; import com.vectras.qemu.utils.Machine; import com.vectras.qemu.utils.QmpClient; import com.vectras.vm.Fragment.ControlersOptionsFragment; import com.vectras.vm.MainActivity; import com.vectras.vm.R; import com.vectras.vm.adapter.LogsAdapter; import com.vectras.vm.utils.UIUtils; import java.io.File; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import org.json.JSONObject; import org.libsdl.app.SDLActivity; import org.libsdl.app.SDLControllerManager; import org.libsdl.app.SDLSurface; /** * SDL Activity */ public class MainSDLActivity extends SDLActivity { public static final String TAG = "MainSDLActivity"; public static MainSDLActivity activity ; public static final int KEYBOARD = 10000; public static final int QUIT = 10001; public static final int HELP = 10002; private boolean monitorMode = false; private boolean mouseOn = false; private Object lockTime = new Object(); private boolean timeQuit = false; private boolean once = true; private boolean zoomable = false; private String status = null; public static int vm_width; public static int vm_height; private Thread timeListenerThread; private ProgressDialog progDialog; protected static ViewGroup mMainLayout; public String cd_iso_path = null; // HDD public String hda_img_path = null; public String hdb_img_path = null; public String hdc_img_path = null; public String hdd_img_path = null; public String fda_img_path = null; public String fdb_img_path = null; public String cpu = null; // Default Settings public int memory = 128; public String bootdevice = null; // net public String net_cfg = "None"; public int nic_num = 1; public String vga_type = "std"; public String hd_cache = "default"; public String nic_driver = null; public String soundcard = null; public String lib = "liblimbo.so"; public String lib_path = null; public String snapshot_name = "limbo"; public int disableacpi = 0; public int disablehpet = 0; public int disabletsc = 0; public int enableqmp = 0; public int enablevnc = 0; public String vnc_passwd = null; public int vnc_allow_external = 0; public String qemu_dev = null; public String qemu_dev_value = null; public String dns_addr = null; public int restart = 0; // This is what SDL runs in. It invokes SDL_main(), eventually private static Thread mSDLThread; // EGL private objects private static EGLContext mEGLContext; private static EGLSurface mEGLSurface; private static EGLDisplay mEGLDisplay; private static EGLConfig mEGLConfig; private static int mGLMajor, mGLMinor; private static Activity activity1; // public static void showTextInput(int x, int y, int w, int h) { // // Transfer the task to the main thread as a Runnable // // mSingleton.commandHandler.post(new ShowTextInputHandler(x, y, w, h)); // } public static void singleClick(final MotionEvent event, final int pointer_id) { Thread t = new Thread(new Runnable() { public void run() { // Log.d("SDL", "Mouse Single Click"); try { Thread.sleep(50); } catch (InterruptedException ex) { // Log.v("singletap", "Could not sleep"); } MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 1,0, 0); try { Thread.sleep(50); } catch (InterruptedException ex) { // Log.v("singletap", "Could not sleep"); } MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 1, 0, 0); } }); t.start(); } public void setParams() { if (MainActivity.vmexecutor == null) { return; } memory = MainActivity.vmexecutor.memory; vga_type = MainActivity.vmexecutor.vga_type; hd_cache = MainActivity.vmexecutor.hd_cache; snapshot_name = MainActivity.vmexecutor.snapshot_name; disableacpi = MainActivity.vmexecutor.disableacpi; disablehpet = MainActivity.vmexecutor.disablehpet; disabletsc = MainActivity.vmexecutor.disabletsc; enableqmp = MainActivity.vmexecutor.enableqmp; enablevnc = MainActivity.vmexecutor.enablevnc; if (MainActivity.vmexecutor.cpu.endsWith("(64Bit)")) { cpu = MainActivity.vmexecutor.cpu.split(" ")[0]; } else { cpu = MainActivity.vmexecutor.cpu; } if (MainActivity.vmexecutor.cd_iso_path == null || MainActivity.vmexecutor.cd_iso_path.equals("None")) { cd_iso_path = null; } else { cd_iso_path = MainActivity.vmexecutor.cd_iso_path; } if (MainActivity.vmexecutor.hda_img_path == null || MainActivity.vmexecutor.hda_img_path.equals("None")) { hda_img_path = null; } else { hda_img_path = MainActivity.vmexecutor.hda_img_path; } if (MainActivity.vmexecutor.hdb_img_path == null || MainActivity.vmexecutor.hdb_img_path.equals("None")) { hdb_img_path = null; } else { hdb_img_path = MainActivity.vmexecutor.hdb_img_path; } if (MainActivity.vmexecutor.hdc_img_path == null || MainActivity.vmexecutor.hdc_img_path.equals("None")) { hdc_img_path = null; } else { hdc_img_path = MainActivity.vmexecutor.hdc_img_path; } if (MainActivity.vmexecutor.hdd_img_path == null || MainActivity.vmexecutor.hdd_img_path.equals("None")) { hdd_img_path = null; } else { hdd_img_path = MainActivity.vmexecutor.hdd_img_path; } if (MainActivity.vmexecutor.fda_img_path == null || MainActivity.vmexecutor.fda_img_path.equals("None")) { fda_img_path = null; } else { fda_img_path = MainActivity.vmexecutor.fda_img_path; } if (MainActivity.vmexecutor.fdb_img_path == null || MainActivity.vmexecutor.fdb_img_path.equals("None")) { fdb_img_path = null; } else { fdb_img_path = MainActivity.vmexecutor.fdb_img_path; } if (MainActivity.vmexecutor.bootdevice == null) { bootdevice = null; } else if (MainActivity.vmexecutor.bootdevice.equals("Default")) { bootdevice = null; } else if (MainActivity.vmexecutor.bootdevice.equals("CD Rom")) { bootdevice = "d"; } else if (MainActivity.vmexecutor.bootdevice.equals("Floppy")) { bootdevice = "a"; } else if (MainActivity.vmexecutor.bootdevice.equals("Hard Disk")) { bootdevice = "c"; } if (MainActivity.vmexecutor.net_cfg == null || MainActivity.vmexecutor.net_cfg.equals("None")) { net_cfg = "none"; nic_driver = null; } else if (MainActivity.vmexecutor.net_cfg.equals("User")) { net_cfg = "user"; nic_driver = MainActivity.vmexecutor.nic_card; } soundcard = MainActivity.vmexecutor.sound_card; } public static void delayKey(int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void sendCtrlAltKey(int code) { delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_LEFT); delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_LEFT); delayKey(100); if(code>=0) { SDLActivity.onNativeKeyDown(code); delayKey(100); SDLActivity.onNativeKeyUp(code); delayKey(100); } SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_LEFT); delayKey(100); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_LEFT); } public void stopTimeListener() { Log.v("SaveVM", "Stopping Listener"); synchronized (this.lockTime) { this.timeQuit = true; this.lockTime.notifyAll(); } } public void onDestroy() { // Now wait for the SDL thread to quit Log.v("MainSDL", "Waiting for SDL thread to quit"); if (mSDLThread != null) { try { mSDLThread.join(); } catch (Exception e) { Log.v("SDL", "Problem stopping thread: " + e); } mSDLThread = null; Log.v("SDL", "Finished waiting for SDL thread"); } this.stopTimeListener(); MainActivity.vmexecutor.doStopVM(0); super.onDestroy(); } public void checkStatus() { while (timeQuit != true) { MainActivity.VMStatus status = Machine.checkSaveVMStatus(activity); Log.v(TAG, "Status: " + status); if (status == MainActivity.VMStatus.Unknown || status == MainActivity.VMStatus.Completed || status == MainActivity.VMStatus.Failed ) { Log.v("Inside", "Saving state is done: " + status); stopTimeListener(); return; } try { Thread.sleep(1000); } catch (InterruptedException ex) { Log.w("SaveVM", "Interrupted"); } } Log.v("SaveVM", "Save state complete"); } public void startTimeListener() { this.stopTimeListener(); timeQuit = false; try { Log.v("Listener", "Time Listener Started..."); checkStatus(); synchronized (lockTime) { while (timeQuit == false) { lockTime.wait(); } lockTime.notifyAll(); } } catch (Exception ex) { ex.printStackTrace(); Log.v("SaveVM", "Time listener thread error: " + ex.getMessage()); } Log.v("Listener", "Time listener thread exited..."); } public static boolean toggleKeyboardFlag = true; @Override public boolean onOptionsItemSelected(final MenuItem item) { // Log.v("Limbo", "Inside Options Check"); super.onOptionsItemSelected(item); if (item.getItemId() == R.id.itemReset) { Machine.resetVM(activity); } else if (item.getItemId() == R.id.itemShutdown) { UIUtils.hideKeyboard(this, mSurface); Machine.stopVM(activity); } else if (item.getItemId() == R.id.itemMouse) { onMouseMode(); } else if (item.getItemId() == this.KEYBOARD || item.getItemId() == R.id.itemKeyboard) { //XXX: need to post after delay to work correctly new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { toggleKeyboardFlag = UIUtils.onKeyboard(activity, toggleKeyboardFlag, mSurface); } }, 200); } else if (item.getItemId() == R.id.itemMonitor) { if (this.monitorMode) { this.onVMConsole(); } else { this.onMonitor(); } } else if (item.getItemId() == R.id.itemVolume) { this.onSelectMenuVol(); } else if (item.getItemId() == R.id.itemSaveState) { this.promptPause(activity); } else if (item.getItemId() == R.id.itemSaveSnapshot) { //TODO: //this.promptStateName(activity); } else if (item.getItemId() == R.id.itemFitToScreen) { onFitToScreen(); } else if (item.getItemId() == R.id.itemStretchToScreen) { onStretchToScreen(); } else if (item.getItemId() == R.id.itemZoomIn) { this.setZoomIn(); } else if (item.getItemId() == R.id.itemZoomOut) { this.setZoomOut(); } else if (item.getItemId() == R.id.itemCtrlAltDel) { this.onCtrlAltDel(); } else if (item.getItemId() == R.id.itemCtrlC) { this.onCtrlC(); } else if (item.getItemId() == R.id.itemOneToOne) { this.onNormalScreen(); } else if (item.getItemId() == R.id.itemZoomable) { this.setZoomable(); } else if (item.getItemId() == this.QUIT) { } else if (item.getItemId() == R.id.itemHelp) { } else if (item.getItemId() == R.id.itemHideToolbar) { this.onHideToolbar(); } else if (item.getItemId() == R.id.itemDisplay) { this.onSelectMenuSDLDisplay(); } else if (item.getItemId() == R.id.itemViewLog) { this.onViewLog(); } // this.canvas.requestFocus(); this.invalidateOptionsMenu(); return true; } public void onViewLog() { FileUtils.viewVectrasLog(this); } public void onHideToolbar(){ ActionBar bar = this.getSupportActionBar(); if (bar != null) { bar.hide(); } } private void onMouseMode() { String [] items = {"Trackpad Mouse (Phone)", "Bluetooth/USB Mouse (Desktop mode)", //Physical mouse for Chromebook, Android x86 PC, or Bluetooth Mouse }; final AlertDialog.Builder mBuilder = new AlertDialog.Builder(this); mBuilder.setTitle("Mouse"); mBuilder.setSingleChoiceItems(items, Config.mouseMode.ordinal(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int i) { switch(i){ case 0: setUIModeMobile(true); break; case 1: promptSetUIModeDesktop(false); break; default: break; } dialog.dismiss(); } }); final AlertDialog alertDialog = mBuilder.create(); alertDialog.show(); } public boolean checkVMResolutionFits() { int width = mLayout.getWidth(); int height = mLayout.getHeight(); ActionBar bar = activity.getSupportActionBar(); if (!MainSettingsManager.getAlwaysShowMenuToolbar(MainSDLActivity.this) && bar != null && bar.isShowing()) { height += bar.getHeight(); } if(vm_width < width && vm_height < height) return true; return false; } public void calibration() { //XXX: No need to calibrate for SDL trackpad. } private void setUIModeMobile(boolean fitToScreen){ try { UIUtils.setOrientation(this); MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); //TODO: needed? //MainSDLActivity.singleClick(a, 0); Config.mouseMode = Config.MouseMode.Trackpad; MainSettingsManager.setDesktopMode(this, false); MainActivity.vmexecutor.setRelativeMouseMode(1); if(Config.showToast) UIUtils.toastShort(this.getApplicationContext(), "Trackpad Enabled"); if(fitToScreen) onFitToScreen(); else onNormalScreen(); calibration(); invalidateOptionsMenu(); }catch (Exception ex){ if(Config.debug) ex.printStackTrace(); } } private void promptSetUIModeDesktop(final boolean mouseMethodAlt) { final AlertDialog alertDialog; alertDialog = new AlertDialog.Builder(activity).create(); alertDialog.setTitle("Desktop Mode"); LinearLayout mLayout = new LinearLayout(this); mLayout.setPadding(20,20,20,20); mLayout.setOrientation(LinearLayout.VERTICAL); TextView textView = new TextView(activity); textView.setVisibility(View.VISIBLE); String desktopInstructions = this.getString(R.string.desktopInstructions); if(!checkVMResolutionFits()){ String resolutionWarning = "Warning: MainActivity.vmexecutor resolution " + vm_width+ "x" + vm_height + " is too high for Desktop Mode. " + "Scaling will be used and Mouse Alignment will not be accurate. " + "Reduce display resolution within the Guest OS for better experience.\n\n"; desktopInstructions = resolutionWarning + desktopInstructions; } textView.setText(desktopInstructions); ScrollView scrollView = new ScrollView(this); scrollView.addView(textView); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); mLayout.addView(scrollView, params); alertDialog.setView(mLayout); alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { setUIModeDesktop(); alertDialog.dismiss(); } }); alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { alertDialog.dismiss(); } }); alertDialog.show(); } private void setUIModeDesktop() { try { MotionEvent a = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); //TODO: needed? //MainSDLActivity.singleClick(a, 0); //TODO: not needed? //SDLActivity.onNativeMouseReset(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); //SDLActivity.onNativeMouseReset(0, 0, MotionEvent.ACTION_MOVE, vm_width, vm_height, 0); Config.mouseMode = Config.MouseMode.External; MainSettingsManager.setDesktopMode(this, true); MainActivity.vmexecutor.setRelativeMouseMode(0); if(Config.showToast) UIUtils.toastShort(MainSDLActivity.this, "External Mouse Enabled"); onNormalScreen(); calibration(); invalidateOptionsMenu(); }catch (Exception ex){ if(Config.debug) ex.printStackTrace(); } } private void onCtrlAltDel() { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_RIGHT); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_RIGHT); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_FORWARD_DEL); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_FORWARD_DEL); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_RIGHT); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_RIGHT); } private void onCtrlC() { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_RIGHT); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_C); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_C); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_RIGHT); } //TODO: not working private void onStretchToScreen() { new Thread(new Runnable() { public void run() { Log.d(TAG, "onStretchToScreen"); screenMode = SDLScreenMode.Fullscreen; sendCtrlAltKey(KeyEvent.KEYCODE_F); // not working if(Config.showToast) UIUtils.toastShort(activity, "Resizing, Please Wait"); resize(null); } }).start(); } private void onFitToScreen() { try { UIUtils.setOrientation(this); ActionBar bar = MainSDLActivity.this.getSupportActionBar(); if (bar != null && !MainSettingsManager.getAlwaysShowMenuToolbar(this)) { bar.hide(); } new Thread(new Runnable() { public void run() { Log.d(TAG, "onFitToScreen"); screenMode = SDLScreenMode.FitToScreen; if(Config.showToast) UIUtils.toastShort(activity, "Resizing, Please Wait"); resize(null); } }).start(); }catch (Exception ex){ if(Config.debug) ex.printStackTrace(); } } private void onNormalScreen() { try { ActionBar bar = MainSDLActivity.this.getSupportActionBar(); if (bar != null && !MainSettingsManager.getAlwaysShowMenuToolbar(this)) { bar.hide(); } setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); new Thread(new Runnable() { public void run() { Log.d(TAG, "onNormalScreen"); screenMode = SDLScreenMode.Normal; if(Config.showToast) UIUtils.toastShort(activity, "Resizing, Please Wait"); resize(null); } }).start(); }catch (Exception ex){ if(Config.debug) ex.printStackTrace(); } } public void resize(final Configuration newConfig) { //XXX: flag so no mouse events are processed isResizing = true; //XXX: This is needed so Nougat+ devices will update their layout new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { ((MainSDLSurface) mSurface).getHolder().setFixedSize(1, 1); setLayout(newConfig); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { ((MainSDLSurface) mSurface).doResize(false, newConfig); } }, 1000); } }); } private void setZoomIn() { new Thread(new Runnable() { public void run() { screenMode = SDLScreenMode.Normal; sendCtrlAltKey(KeyEvent.KEYCODE_4); } }).start(); } private void setZoomOut() { new Thread(new Runnable() { public void run() { screenMode = SDLScreenMode.Normal; sendCtrlAltKey(KeyEvent.KEYCODE_3); } }).start(); } private void setZoomable() { zoomable = true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); return this.setupMenu(menu); } @Override public boolean onCreateOptionsMenu(Menu menu) { menu.clear(); return this.setupMenu(menu); } public boolean setupMenu(Menu menu) { // Log.v("Limbo", "Inside Options Created"); getMenuInflater().inflate(R.menu.sdlactivitymenu, menu); int maxMenuItemsShown = 4; int actionShow = MenuItemCompat.SHOW_AS_ACTION_IF_ROOM; if(UIUtils.isLandscapeOrientation(this)) { maxMenuItemsShown = 6; actionShow = MenuItemCompat.SHOW_AS_ACTION_ALWAYS; } // if (vncCanvas.scaling != null) { // menu.findItem(vncCanvas.scaling.getId()).setChecked(true); // } // Remove snapshots for now menu.removeItem(menu.findItem(R.id.itemSaveSnapshot).getItemId()); // Remove Monitor console for SDL2 it creates 2 SDL windows and SDL for // android supports only 1 menu.removeItem(menu.findItem(R.id.itemMonitor).getItemId()); // Remove scaling for now menu.removeItem(menu.findItem(R.id.itemScaling).getItemId()); // Remove external mouse for now menu.removeItem(menu.findItem(R.id.itemExternalMouse).getItemId()); //menu.removeItem(menu.findItem(R.id.itemUIMode).getItemId()); menu.removeItem(menu.findItem(R.id.itemCtrlAltDel).getItemId()); menu.removeItem(menu.findItem(R.id.itemCtrlC).getItemId()); if (MainSettingsManager.getAlwaysShowMenuToolbar(activity) || Config.mouseMode == Config.MouseMode.External) { menu.removeItem(menu.findItem(R.id.itemHideToolbar).getItemId()); maxMenuItemsShown--; } if (soundcard==null || soundcard.equals("None")) { menu.removeItem(menu.findItem(R.id.itemVolume).getItemId()); maxMenuItemsShown--; } for (int i = 0; i < menu.size() && i < maxMenuItemsShown; i++) { MenuItemCompat.setShowAsAction(menu.getItem(i), actionShow); } return true; } private void onMonitor() { new Thread(new Runnable() { public void run() { monitorMode = true; // final KeyEvent altDown = new KeyEvent(downTime, eventTime, // KeyEvent.ACTION_DOWN, // KeyEvent.KEYCODE_2, 1, KeyEvent.META_ALT_LEFT_ON); sendCtrlAltKey(KeyEvent.KEYCODE_2); // sendCtrlAltKey(altDown); Log.v("Limbo", "Monitor On"); } }).start(); } private void onVMConsole() { monitorMode = false; sendCtrlAltKey(KeyEvent.KEYCODE_1); } // FIXME: We need this to able to catch complex characters strings like // grave and send it as text @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_MULTIPLE && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { sendText(event.getCharacters().toString()); return true; } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { this.onBackPressed(); return true; } if (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) { // We emulate right click with volume down if(event.getAction() == KeyEvent.ACTION_DOWN) { MotionEvent e = MotionEvent.obtain(1000, 1000, MotionEvent.ACTION_DOWN, 0, 0, 0, 0, 0, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); rightClick(e, 0); } return true; } else if (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) { // We emulate middle click with volume up if(event.getAction() == KeyEvent.ACTION_DOWN) { MotionEvent e = MotionEvent.obtain(1000, 1000, MotionEvent.ACTION_DOWN, 0, 0, 0, 0, 0, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); middleClick(e, 0); } return true; } else { return super.dispatchKeyEvent(event); } } private static void sendText(String string) { // Log.v("sendText", string); KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); KeyEvent[] keyEvents = keyCharacterMap.getEvents(string.toCharArray()); if (keyEvents != null) for (int i = 0; i < keyEvents.length; i++) { if (keyEvents[i].getAction() == KeyEvent.ACTION_DOWN) { // Log.v("sendText", "Up: " + keyEvents[i].getKeyCode()); SDLActivity.onNativeKeyDown(keyEvents[i].getKeyCode()); } else if (keyEvents[i].getAction() == KeyEvent.ACTION_UP) { // Log.v("sendText", "Down: " + keyEvents[i].getKeyCode()); SDLActivity.onNativeKeyUp(keyEvents[i].getKeyCode()); } } } String[] functionsArray = {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"}; View view; private final String CREDENTIAL_SHARED_PREF = "settings_prefs"; private LogsAdapter mLogAdapter; private RecyclerView logList; private Timer _timer = new Timer(); private TimerTask t; public boolean ctrlClicked = false; public boolean altClicked = false; // Setup @SuppressLint("UseCompatLoadingForDrawables") protected void onCreate(Bundle savedInstanceState) { // Log.v("SDL", "onCreate()"); activity = this; if (MainSettingsManager.getFullscreen(this)) getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); setupVolume(); mSingleton = this; Log.v("SDL", "Max Mem = " + Runtime.getRuntime().maxMemory()); this.activity1 = this; // So we can call stuff from static callbacks mSingleton = this; createUI(0, 0); UIUtils.showHints(this); this.resumeVM(); UIUtils.setOrientation(this); View decorView = getWindow().getDecorView(); int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; decorView.setSystemUiVisibility(uiOptions); onFitToScreen(); ImageButton shutdownBtn = findViewById(R.id.shutdownBtn); ImageButton settingBtn = findViewById(R.id.btnSettings); 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); Button ctrlAltDelBtn = findViewById(R.id.ctrlAltDelBtn); ImageButton qmpBtn = findViewById(R.id.btnQmp); ImageButton btnLogs = findViewById(R.id.btnLogs); btnLogs.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileUtils.viewVectrasLog(activity); } }); shutdownBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Machine.stopVM(activity); } }); keyboardBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { toggleKeyboardFlag = UIUtils.onKeyboard(activity, toggleKeyboardFlag, mSurface); } }, 200); } }); controllersBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); // Create and show the dialog. ControlersOptionsFragment newFragment = new ControlersOptionsFragment(); newFragment.show(ft, "Controllers"); } }); settingBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final Dialog alertDialog = new Dialog(activity, R.style.MainDialogTheme); alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); alertDialog.setContentView(R.layout.dialog_setting); alertDialog.show(); } }); upBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_DPAD_UP); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_DPAD_UP); return true; } return false; } }); leftBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_DPAD_LEFT); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_DPAD_LEFT); return true; } return false; } }); downBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_DPAD_DOWN); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_DPAD_DOWN); return true; } return false; } }); rightBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT); return true; } else if (event.getAction() == MotionEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_DPAD_RIGHT); return true; } return false; } }); escBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ESCAPE); delayKey(100); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ESCAPE); } }); enterBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ENTER); delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ENTER); } }); ctrlBtn.setOnClickListener(new View.OnClickListener() { @SuppressLint("UseCompatLoadingForDrawables") @Override public void onClick(View v) { if (!ctrlClicked) { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_LEFT); ctrlBtn.setBackground(getResources().getDrawable(R.drawable.controls_button2)); ctrlClicked = true; } else { SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_LEFT); 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) { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_ALT_LEFT); altBtn.setBackground(getResources().getDrawable(R.drawable.controls_button2)); altClicked = true; } else { SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_ALT_LEFT); altBtn.setBackground(getResources().getDrawable(R.drawable.controls_button1)); altClicked = false; } } }); delBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_DEL); delayKey(100); SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_DEL); } }); ctrlAltDelBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendCtrlAltKey(KeyEvent.KEYCODE_DEL); } }); qmpBtn.setVisibility(View.GONE); if (monitorMode) { qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_terminal_24)); } else { qmpBtn.setImageDrawable(getResources().getDrawable(R.drawable.round_computer_24)); } ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.container_function, functionsArray); ListView listView = findViewById(R.id.functions); listView.setAdapter(adapter); /*listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { if (position == 0) { keyDownUp(KeyEvent.KEYCODE_F1); } else if (position == 1) { keyDownUp(KeyEvent.KEYCODE_F2); } else if (position == 2) { keyDownUp(KeyEvent.KEYCODE_F3); } else if (position == 3) { keyDownUp(KeyEvent.KEYCODE_F4); } else if (position == 4) { keyDownUp(KeyEvent.KEYCODE_F5); } else if (position == 5) { keyDownUp(KeyEvent.KEYCODE_F6); } else if (position == 6) { keyDownUp(KeyEvent.KEYCODE_F7); } else if (position == 7) { keyDownUp(KeyEvent.KEYCODE_F8); } else if (position == 8) { keyDownUp(KeyEvent.KEYCODE_F9); } else if (position == 9) { keyDownUp(KeyEvent.KEYCODE_F10); } else if (position == 10) { keyDownUp(KeyEvent.KEYCODE_F11); } else if (position == 11) { keyDownUp(KeyEvent.KEYCODE_F12); } } });*/ } private void createUI(int w, int h) { mSurface = new MainSDLSurface(this); int width = w; int height = h; if (width == 0) { width = RelativeLayout.LayoutParams.WRAP_CONTENT; } if (height == 0) { height = RelativeLayout.LayoutParams.WRAP_CONTENT; } setContentView(R.layout.activity_sdl); //TODO: mLayout = (RelativeLayout) activity.findViewById(R.id.sdl_layout); mMainLayout = (LinearLayout) activity.findViewById(R.id.main_layout); RelativeLayout mLayout = (RelativeLayout) findViewById(R.id.sdl); RelativeLayout.LayoutParams surfaceParams = new RelativeLayout.LayoutParams(width, height); surfaceParams.addRule(RelativeLayout.CENTER_IN_PARENT); mLayout.addView(mSurface, surfaceParams); } protected void onPause() { Log.v("SDL", "onPause()"); MainService.updateServiceNotification("Vectras VM Suspended"); super.onPause(); } public void onSelectMenuVol() { final AlertDialog alertDialog; alertDialog = new AlertDialog.Builder(activity).create(); alertDialog.setTitle("Volume"); LinearLayout.LayoutParams volParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout t = createVolumePanel(); t.setLayoutParams(volParams); ScrollView s = new ScrollView(activity); s.addView(t); alertDialog.setView(s); alertDialog.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { alertDialog.cancel(); } }); alertDialog.show(); } public LinearLayout createVolumePanel() { LinearLayout layout = new LinearLayout (this); layout.setPadding(20, 20, 20, 20); LinearLayout.LayoutParams volparams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); SeekBar vol = new SeekBar(this); int volume = 0; //TODO: vol.setMax(maxVolume); volume = getCurrentVolume(); vol.setProgress(volume); vol.setLayoutParams(volparams); ((SeekBar) vol).setOnSeekBarChangeListener(new OnSeekBarChangeListener() { public void onProgressChanged(SeekBar s, int progress, boolean touch) { //TODO: setVolume(progress); } public void onStartTrackingTouch(SeekBar arg0) { } public void onStopTrackingTouch(SeekBar arg0) { } }); layout.addView(vol); return layout; } protected void onResume() { Log.v("SDL", "onResume()"); MainService.updateServiceNotification("Vectras VM Running"); super.onResume(); } // Messages from the SDLMain thread static int COMMAND_CHANGE_TITLE = 1; static int COMMAND_SAVEVM = 2; public void loadLibraries() { //XXX: override for the specific arch } public void promptPause(final Activity activity) { final AlertDialog alertDialog; alertDialog = new AlertDialog.Builder(activity).create(); alertDialog.setTitle("Pause VM"); TextView stateView = new TextView(activity); stateView.setText("This make take a while depending on the RAM size used"); stateView.setPadding(20, 20, 20, 20); alertDialog.setView(stateView); alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Pause", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { onPauseVM(); return; } }); alertDialog.show(); } private void onPauseVM() { Thread t = new Thread(new Runnable() { public void run() { // Delete any previous state file if (MainActivity.vmexecutor.save_state_name != null) { File file = new File(MainActivity.vmexecutor.save_state_name); if (file.exists()) { file.delete(); } } if(Config.showToast) UIUtils.toastShort(getApplicationContext(), "Please wait while saving VM State"); MainActivity.vmexecutor.current_fd = MainActivity.vmexecutor.get_fd(MainActivity.vmexecutor.save_state_name); String uri = "fd:" + MainActivity.vmexecutor.current_fd; String command = QmpClient.stop(); String msg = QmpClient.sendCommand(command); command = QmpClient.migrate(false, false, uri); msg = QmpClient.sendCommand(command); if (msg != null) { processMigrationResponse(msg); } // XXX: Instead we poll to see if migration is complete new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { VMListener a = new VMListener(); a.execute(); } }, 0); } }); t.start(); } private void processMigrationResponse(String response) { String errorStr = null; try { JSONObject object = new JSONObject(response); errorStr = object.getString("error"); }catch (Exception ex) { if(Config.debug) ex.printStackTrace(); } if (errorStr != null) { String descStr = null; try { JSONObject descObj = new JSONObject(errorStr); descStr = descObj.getString("desc"); }catch (Exception ex) { if(Config.debug) ex.printStackTrace(); } final String descStr1 = descStr; new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { Machine.pausedErrorVM(activity, descStr1); } }, 100); } } private class VMListener extends AsyncTask { @Override protected Void doInBackground(Void... arg0) { startTimeListener(); return null; } @Override protected void onPostExecute(Void test) { } } @Override public boolean onTouchEvent(MotionEvent event) { boolean res = false; if(Config.mouseMode == Config.MouseMode.External){ return res; } //TODO: res = ((MainSDLSurface) this.mSurface).onTouchProcess(this.mSurface, event); res = ((MainSDLSurface) this.mSurface).onTouchEventProcess(event); return true; } private void resumeVM() { if(MainActivity.vmexecutor == null){ return; } Thread t = new Thread(new Runnable() { public void run() { if (MainActivity.vmexecutor.paused == 1) { try { Thread.sleep(4000); } catch (InterruptedException ex) { Logger.getLogger(MainVNCActivity.class.getName()).log(Level.SEVERE, null, ex); } MainActivity.vmexecutor.paused = 0; String command = QmpClient.cont(); String msg = QmpClient.sendCommand(command); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { if(Config.mouseMode == Config.MouseMode.External) setUIModeDesktop(); else setUIModeMobile(screenMode == SDLScreenMode.FitToScreen); } }, 500); } } }); t.start(); } public void onBackPressed() { super.onBackPressed(); UIUtils.hideKeyboard(this, mSurface); Machine.stopVM(activity); return; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); this.invalidateOptionsMenu(); } public void onSelectMenuSDLDisplay() { final AlertDialog alertDialog; alertDialog = new AlertDialog.Builder(activity).create(); alertDialog.setTitle("Display"); LinearLayout.LayoutParams volParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout t = createSDLDisplayPanel(); t.setLayoutParams(volParams); ScrollView s = new ScrollView(activity); s.addView(t); alertDialog.setView(s); alertDialog.setButton(Dialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { alertDialog.cancel(); } }); alertDialog.show(); } public LinearLayout createSDLDisplayPanel() { LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); layout.setPadding(20, 20, 20, 20); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); int currRate = getCurrentSDLRefreshRate(); LinearLayout buttonsLayout = new LinearLayout(this); buttonsLayout.setOrientation(LinearLayout.HORIZONTAL); buttonsLayout.setGravity(Gravity.CENTER_HORIZONTAL); Button displayMode = new Button (this); displayMode.setText("Display Mode"); displayMode.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { onDisplayMode(); } }); buttonsLayout.addView(displayMode); layout.addView(buttonsLayout); final TextView value = new TextView(this); value.setText("Idle Refresh Rate: " + currRate+" Hz"); layout.addView(value); value.setLayoutParams(params); SeekBar rate = new SeekBar(this); rate.setMax(Config.MAX_DISPLAY_REFRESH_RATE); rate.setProgress(currRate); rate.setLayoutParams(params); ((SeekBar) rate).setOnSeekBarChangeListener(new OnSeekBarChangeListener() { public void onProgressChanged(SeekBar s, int progress, boolean touch) { value.setText("Idle Refresh Rate: " + (progress+1)+" Hz"); } public void onStartTrackingTouch(SeekBar arg0) { } public void onStopTrackingTouch(SeekBar arg0) { int progress = arg0.getProgress()+1; int refreshMs = 1000 / progress; Log.v(TAG, "Changing idle refresh rate: (ms)" + refreshMs); MainActivity.vmexecutor.setsdlrefreshrate(refreshMs); } }); layout.addView(rate); return layout; } public int getCurrentSDLRefreshRate() { return 1000 / MainActivity.vmexecutor.getsdlrefreshrate(); } private void onDisplayMode() { String [] items = { "Normal (One-To-One)", "Fit To Screen" // ,"Stretch To Screen" //Stretched }; int currentScaleType = 0; if(screenMode == SDLScreenMode.FitToScreen){ currentScaleType = 1; } else if(screenMode == SDLScreenMode.Fullscreen) currentScaleType = 2; final AlertDialog.Builder mBuilder = new AlertDialog.Builder(this); mBuilder.setTitle("Display Mode"); mBuilder.setSingleChoiceItems(items, currentScaleType, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int i) { switch(i){ case 0: onNormalScreen(); break; case 1: if(Config.mouseMode == Config.MouseMode.External){ UIUtils.toastShort(MainSDLActivity.this, "Fit to Screen Disabled under Desktop Mode"); dialog.dismiss(); return; } onFitToScreen(); break; case 2: if(Config.mouseMode == Config.MouseMode.External){ UIUtils.toastShort(MainSDLActivity.this, "Stretch Screen Disabled under Desktop Mode"); dialog.dismiss(); return; } onStretchToScreen(); break; default: break; } dialog.dismiss(); } }); final AlertDialog alertDialog = mBuilder.create(); alertDialog.show(); } @Override protected synchronized void runSDLMain(){ //We go through the vm executor MainActivity.startvm(this, Config.UI_SDL); //XXX: we hold the thread because SDLActivity will exit try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void onVMResolutionChanged(int w, int h) { boolean refreshDisplay = false; if(w!=vm_width || h!=vm_height) refreshDisplay = true; vm_width = w; vm_height = h; Log.v(TAG, "VM resolution changed to " + vm_width + "x" + vm_height); if(refreshDisplay) { activity.resize(null); } } public static boolean isResizing = false; public enum SDLScreenMode { Normal, FitToScreen, Fullscreen //fullscreen not implemented yet } public SDLScreenMode screenMode = SDLScreenMode.FitToScreen; private void setLayout(Configuration newConfig) { boolean isLanscape = (newConfig!=null && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) || UIUtils.isLandscapeOrientation(this); View vnc_canvas_layout = (View) this.findViewById(R.id.sdl_layout); RelativeLayout.LayoutParams vnc_canvas_layout_params = null; //normal 1-1 if(screenMode == SDLScreenMode.Normal) { if (isLanscape) { vnc_canvas_layout_params = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); // vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_IN_PARENT); vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP); vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL); } else { vnc_canvas_layout_params = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP); vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL); } } else { //fittoscreen if (isLanscape) { vnc_canvas_layout_params = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ); vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_IN_PARENT); } else { vnc_canvas_layout_params = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); vnc_canvas_layout_params.addRule(RelativeLayout.ALIGN_PARENT_TOP); vnc_canvas_layout_params.addRule(RelativeLayout.CENTER_HORIZONTAL); } } vnc_canvas_layout.setLayoutParams(vnc_canvas_layout_params); this.invalidateOptionsMenu(); } public class MainSDLSurface extends ExSDLSurface implements View.OnKeyListener, View.OnTouchListener { public boolean initialized = false; public MainSDLSurface(Context context) { super(context); setOnKeyListener(this); setOnTouchListener(this); gestureDetector = new GestureDetector(activity, new GestureListener()); setOnGenericMotionListener(new SDLGenericMotionListener_API12()); } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { super.surfaceChanged(holder, format, width, height); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { SDLActivity.onNativeKeyDown(KeyEvent.KEYCODE_CTRL_LEFT); SDLActivity.onNativeKeyUp(KeyEvent.KEYCODE_CTRL_LEFT); } }, 500); } @Override public void surfaceCreated(SurfaceHolder holder) { super.surfaceCreated(holder); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { if(Config.mouseMode == Config.MouseMode.External) setUIModeDesktop(); else setUIModeMobile(screenMode == SDLScreenMode.FitToScreen); } },1000); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Log.d(TAG,"Configuration changed"); resize(newConfig); } public synchronized void doResize(boolean reverse, final Configuration newConfig) { //XXX: notify the UI not to process mouse motion isResizing = true; Log.v(TAG, "Resizing Display"); Display display = SDLActivity.mSingleton.getWindowManager().getDefaultDisplay(); int height = 0; int width = 0; Point size = new Point(); display.getSize(size); int screen_width = size.x; int screen_height = size.y; final ActionBar bar = ((SDLActivity) activity).getSupportActionBar(); if(MainSDLActivity.mLayout != null) { width = MainSDLActivity.mLayout.getWidth(); height = MainSDLActivity.mLayout.getHeight(); } //native resolution for use with external mouse if(screenMode != SDLScreenMode.Fullscreen && screenMode != SDLScreenMode.FitToScreen) { width = MainSDLActivity.vm_width; height = MainSDLActivity.vm_height; } if(reverse){ int temp = width; width = height; height = temp; } boolean portrait = SDLActivity.mSingleton.getResources() .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; if (portrait) { if(Config.mouseMode != Config.MouseMode.External) { int height_n = (int) (width / (MainSDLActivity.vm_width / (float) MainSDLActivity.vm_height)); Log.d(TAG, "Resizing portrait: " + width + " x " + height_n); getHolder().setFixedSize(width, height_n); } } else { if ( (screenMode == SDLScreenMode.Fullscreen || screenMode == SDLScreenMode.FitToScreen) && !MainSettingsManager.getAlwaysShowMenuToolbar(MainSDLActivity.this) && bar != null && bar.isShowing()) { height += bar.getHeight(); } Log.d(TAG, "Resizing landscape: " + width + " x " + height); getHolder().setFixedSize(width, height); } initialized = true; new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { isResizing = false; } }, 1000); } // XXX: SDL is missing some key codes in sdl2-keymap.h // So we create them with a Shift Modifier private boolean handleMissingKeys(int keyCode, int action) { int keyCodeTmp = keyCode; switch (keyCode) { case 77: keyCodeTmp = 9; break; case 81: keyCodeTmp = 70; break; case 17: keyCodeTmp = 15; break; case 18: keyCodeTmp = 10; break; default: return false; } if (action == KeyEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(59); SDLActivity.onNativeKeyDown(keyCodeTmp); } else { SDLActivity.onNativeKeyUp(59); SDLActivity.onNativeKeyUp(keyCodeTmp); } return true; } @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { // dismiss android back and forward keys return true; } else if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) { return false; } else if (event.getAction() == KeyEvent.ACTION_DOWN) { //Log.v("SDL", "key down: " + keyCode); if (!handleMissingKeys(keyCode, event.getAction())) SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { //Log.v("SDL", "key up: " + keyCode); if (!handleMissingKeys(keyCode, event.getAction())) SDLActivity.onNativeKeyUp(keyCode); return true; } else { return super.onKey(v, keyCode, event); } } // Touch events public boolean onTouchProcess(View v, MotionEvent event) { int action = event.getAction(); float x = event.getX(0); float y = event.getY(0); float p = event.getPressure(0); int relative = Config.mouseMode == Config.MouseMode.External? 0: 1; int sdlMouseButton = 0; if(event.getButtonState() == MotionEvent.BUTTON_PRIMARY) sdlMouseButton = Config.SDL_MOUSE_LEFT; else if(event.getButtonState() == MotionEvent.BUTTON_SECONDARY) sdlMouseButton = Config.SDL_MOUSE_RIGHT; else if(event.getButtonState() == MotionEvent.BUTTON_TERTIARY) sdlMouseButton = Config.SDL_MOUSE_MIDDLE; if (event.getAction() == MotionEvent.ACTION_MOVE) { if (mouseUp) { old_x = x; old_y = y; mouseUp = false; } if (action == MotionEvent.ACTION_MOVE) { if(Config.mouseMode == Config.MouseMode.External) { //Log.d("SDL", "onTouch Absolute Move by=" + action + ", X,Y=" + (x) + "," + (y) + " P=" + p); MainActivity.vmexecutor.onVectrasMouse(0, MotionEvent.ACTION_MOVE,0, x , y ); }else { //Log.d("SDL", "onTouch Relative Moving by=" + action + ", X,Y=" + (x - // old_x) + "," + (y - old_y) + " P=" + p); MainActivity.vmexecutor.onVectrasMouse(0, MotionEvent.ACTION_MOVE,1, (x - old_x) * sensitivity_mult, (y - old_y) * sensitivity_mult); } } // save current old_x = x; old_y = y; } else if (event.getAction() == event.ACTION_UP ) { //Log.d("SDL", "onTouch Up: " + sdlMouseButton); //XXX: it seems that the Button state is not available when Button up so // we should release all mouse buttons to be safe since we don't know which one fired the event if(sdlMouseButton == Config.SDL_MOUSE_MIDDLE ||sdlMouseButton == Config.SDL_MOUSE_RIGHT ) { MainActivity.vmexecutor.onVectrasMouse(sdlMouseButton, MotionEvent.ACTION_UP, relative, x, y); } else if (sdlMouseButton != 0) { MainActivity.vmexecutor.onVectrasMouse(sdlMouseButton, MotionEvent.ACTION_UP, relative, x, y); } else { // if we don't have inforamtion about which button we can make some guesses //Or only the last one pressed if (lastMouseButtonDown > 0) { if(lastMouseButtonDown == Config.SDL_MOUSE_MIDDLE ||lastMouseButtonDown == Config.SDL_MOUSE_RIGHT ) { MainActivity.vmexecutor.onVectrasMouse(lastMouseButtonDown, MotionEvent.ACTION_UP, relative,x, y); }else MainActivity.vmexecutor.onVectrasMouse(lastMouseButtonDown, MotionEvent.ACTION_UP, relative, x, y); } else { //ALl buttons if (Config.mouseMode == Config.MouseMode.Trackpad) { MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 1, 0, 0); } else if (Config.mouseMode == Config.MouseMode.External) { MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 0, x, y); MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_RIGHT, MotionEvent.ACTION_UP, 0, x, y); MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_MIDDLE, MotionEvent.ACTION_UP, 0, x, y); } } } lastMouseButtonDown = -1; mouseUp = true; } else if (event.getAction() == event.ACTION_DOWN && Config.mouseMode == Config.MouseMode.External ) { //XXX: Some touch events for touchscreen mode are primary so we force left mouse button if(sdlMouseButton == 0 && MotionEvent.TOOL_TYPE_FINGER == event.getToolType(0)) { sdlMouseButton = Config.SDL_MOUSE_LEFT; } MainActivity.vmexecutor.onVectrasMouse(sdlMouseButton, MotionEvent.ACTION_DOWN, relative, x, y); lastMouseButtonDown = sdlMouseButton; } return true; } public boolean onTouch(View v, MotionEvent event) { boolean res = false; if(Config.mouseMode == Config.MouseMode.External){ res = onTouchProcess(v,event); res = onTouchEventProcess(event); } return res; } public boolean onTouchEvent(MotionEvent event) { return false; } public boolean onTouchEventProcess(MotionEvent event) { // Log.v("onTouchEvent", // "Action=" + event.getAction() + ", X,Y=" + event.getX() + "," // + event.getY() + " P=" + event.getPressure()); // MK if (event.getAction() == MotionEvent.ACTION_CANCEL) return true; if (!firstTouch) { firstTouch = true; } if (event.getPointerCount() > 1) { // XXX: Limbo Legacy enable Right Click with 2 finger touch // Log.v("Right Click", // "Action=" + event.getAction() + ", X,Y=" + event.getX() // + "," + event.getY() + " P=" + event.getPressure()); // rightClick(event); return true; } else return gestureDetector.onTouchEvent(event); } } public AudioManager am; protected int maxVolume; protected void setupVolume() { if (am == null) { am = (AudioManager) mSingleton.getSystemService(Context.AUDIO_SERVICE); maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); } } public void setVolume(int volume) { if(am!=null) am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); } protected int getCurrentVolume() { int volumeTmp = 0; if(am!=null) volumeTmp = am.getStreamVolume(AudioManager.STREAM_MUSIC); return volumeTmp; } //XXX: We want to suspend only when app is calling onPause() @Override public void onWindowFocusChanged(boolean hasFocus) { } public boolean rightClick(final MotionEvent e, final int i) { Thread t = new Thread(new Runnable() { public void run() { Log.d("SDL", "Mouse Right Click"); MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_RIGHT, MotionEvent.ACTION_DOWN, 1, -1, -1); try { Thread.sleep(100); } catch (InterruptedException ex) { // Log.v("SDLSurface", "Interrupted: " + ex); } MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_RIGHT, MotionEvent.ACTION_UP, 1, -1, -1); } }); t.start(); return true; } public boolean middleClick(final MotionEvent e, final int i) { Thread t = new Thread(new Runnable() { public void run() { Log.d("SDL", "Mouse Middle Click"); MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_MIDDLE, MotionEvent.ACTION_DOWN, 1,-1, -1); try { Thread.sleep(100); } catch (InterruptedException ex) { // Log.v("SDLSurface", "Interrupted: " + ex); } MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_MIDDLE, MotionEvent.ACTION_UP, 1,-1, -1); } }); t.start(); return true; } private void doubleClick(final MotionEvent event, final int pointer_id) { Thread t = new Thread(new Runnable() { public void run() { //Log.d("SDL", "Mouse Double Click"); for (int i = 0; i < 2; i++) { MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 1, 0, 0); try { Thread.sleep(50); } catch (InterruptedException ex) { // Log.v("doubletap", "Could not sleep"); } MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_UP, 1,0, 0); try { Thread.sleep(50); } catch (InterruptedException ex) { // Log.v("doubletap", "Could not sleep"); } } } }); t.start(); } int lastMouseButtonDown = -1; public float old_x = 0; public float old_y = 0; private boolean mouseUp = true; private float sensitivity_mult = (float) 1.0; private boolean firstTouch = false; private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent event) { // Log.v("onDown", "Action=" + event.getAction() + ", X,Y=" + event.getX() // + "," + event.getY() + " P=" + event.getPressure()); return true; } @Override public void onLongPress(MotionEvent event) { // Log.d("SDL", "Long Press Action=" + event.getAction() + ", X,Y=" // + event.getX() + "," + event.getY() + " P=" // + event.getPressure()); if(Config.mouseMode == Config.MouseMode.External) return; if(Config.enableDragOnLongPress) dragPointer(event); } public boolean onSingleTapConfirmed(MotionEvent event) { float x1 = event.getX(); float y1 = event.getY(); if(Config.mouseMode == Config.MouseMode.External) return true; // Log.d("onSingleTapConfirmed", "Tapped at: (" + x1 + "," + y1 + // ")"); for (int i = 0; i < event.getPointerCount(); i++) { int action = event.getAction(); float x = event.getX(i); float y = event.getY(i); float p = event.getPressure(i); //Log.v("onSingleTapConfirmed", "Action=" + action + ", X,Y=" + x + "," + y + " P=" + p); if (event.getAction() == event.ACTION_DOWN && MotionEvent.TOOL_TYPE_FINGER == event.getToolType(0)) { //Log.d("SDL", "onTouch Down: " + event.getButtonState()); MainSDLActivity.singleClick(event, i); } } return true; } // event when double tap occurs @Override public boolean onDoubleTap(MotionEvent event) { // Log.d("onDoubleTap", "Tapped at: (" + event.getX() + "," + event.getY() + ")"); if(Config.mouseMode == Config.MouseMode.External //&& MotionEvent.TOOL_TYPE_MOUSE == event.getToolType(0) ) return true; if(!Config.enableDragOnLongPress) processDoubleTap(event); else doubleClick(event, 0); return true; } } private void dragPointer(MotionEvent event) { MainActivity.vmexecutor.onVectrasMouse(Config.SDL_MOUSE_LEFT, MotionEvent.ACTION_DOWN, 1, 0, 0); Vibrator v = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); if (v.hasVibrator()) { v.vibrate(100); } } private void processDoubleTap(final MotionEvent event) { Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(400); } catch (InterruptedException e1) { e1.printStackTrace(); } if (!mouseUp) { dragPointer(event); } else { // Log.v("onDoubleTap", "Action=" + action + ", X,Y=" + x + "," + y + " P=" + p); doubleClick(event, 0); } } }); t.start(); } class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { private MainSDLSurface mSurface; @Override public boolean onGenericMotion(View v, MotionEvent event) { float x, y; int action; switch (event.getSource()) { case InputDevice.SOURCE_JOYSTICK: case InputDevice.SOURCE_GAMEPAD: case InputDevice.SOURCE_DPAD: SDLControllerManager.handleJoystickMotionEvent(event); return true; case InputDevice.SOURCE_MOUSE: if(Config.mouseMode == Config.MouseMode.Trackpad) break; action = event.getActionMasked(); // Log.d("SDL", "onGenericMotion, action = " + action + "," + event.getX() + ", " + event.getY()); switch (action) { case MotionEvent.ACTION_SCROLL: x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); // Log.d("SDL", "Mouse Scroll: " + x + "," + y); MainActivity.vmexecutor.onVectrasMouse(0, action, 0, x, y); return true; case MotionEvent.ACTION_HOVER_MOVE: if(Config.processMouseHistoricalEvents) { final int historySize = event.getHistorySize(); for (int h = 0; h < historySize; h++) { float ex = event.getHistoricalX(h); float ey = event.getHistoricalY(h); float ep = event.getHistoricalPressure(h); processHoverMouse(ex, ey, ep, action); } } float ex = event.getX(); float ey = event.getY(); float ep = event.getPressure(); processHoverMouse(ex, ey, ep, action); return true; case MotionEvent.ACTION_UP: default: break; } break; default: break; } // Event was not managed return false; } private void processHoverMouse(float x,float y,float p, int action) { if(Config.mouseMode == Config.MouseMode.External) { //Log.d("SDL", "Mouse Hover: " + x + "," + y); MainActivity.vmexecutor.onVectrasMouse(0, action, 0, x, y); } } } GestureDetector gestureDetector; }