From 39cf8ed6cae6dae7d95ba1f454dcac3eae12a564 Mon Sep 17 00:00:00 2001 From: QuantMad Date: Wed, 24 Aug 2022 20:29:42 +0300 Subject: [PATCH 1/2] Added the ability to customize keyboard shortcuts --- .../droidvnc_ng/HotkeyBinding.java | 55 ++ .../droidvnc_ng/InputService.java | 511 +++++++++--------- .../droidvnc_ng/MainActivity.java | 75 +++ .../christianbeier/droidvnc_ng/VNCKey.java | 84 +++ app/src/main/res/layout/activity_main.xml | 40 +- app/src/main/res/values/strings.xml | 1 + 6 files changed, 499 insertions(+), 267 deletions(-) create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/HotkeyBinding.java create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/VNCKey.java diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/HotkeyBinding.java b/app/src/main/java/net/christianbeier/droidvnc_ng/HotkeyBinding.java new file mode 100644 index 00000000..bc438869 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/HotkeyBinding.java @@ -0,0 +1,55 @@ +package net.christianbeier.droidvnc_ng; + +public class HotkeyBinding implements Comparable { + private final VNCKey[] keys = new VNCKey[3]; + // placeholder for command to execute by this hotkey + private final ICommand command; + // user-friendly name. Uses in GUI + public final String friendlyName; + // uses for settings serialization + public final String PREFS_NAME; + + public HotkeyBinding(String friendlyName, String prefsName, VNCKey key1, VNCKey key2, VNCKey key3, ICommand command) { + keys[0] = key1; + keys[1] = key2; + keys[2] = key3; + this.command = command; + this.friendlyName = friendlyName; + this.PREFS_NAME = prefsName; + } + + public boolean isTriggered() { + for (VNCKey key : keys) { + if (key != null && key != VNCKey.Empty && key.isNotDown()) + return false; + } + return true; + } + + public boolean tryExecute() { + if (isTriggered()) { + command.execute(); + return true; + } + return false; + } + + public VNCKey getKey(int index) { + if (index > 3) return VNCKey.Empty; + return keys[index]; + } + + public void setKey(int index, VNCKey key) { + if (index > 3) return; + keys[index] = key; + } + + @Override + public int compareTo(HotkeyBinding hotkeyBinding) { + return hotkeyBinding.keys.length - keys.length; + } + + public interface ICommand { + void execute(); + } +} diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/InputService.java b/app/src/main/java/net/christianbeier/droidvnc_ng/InputService.java index a08b5e57..6d6901b9 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/InputService.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/InputService.java @@ -12,6 +12,8 @@ * * Swipe fixes and gesture handling by Christian Beier . * + * Expanded hotkey system Alexander Balanyuk Anatolevich QuantMad@gmail.com + * */ import android.accessibilityservice.AccessibilityService; @@ -19,295 +21,282 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.graphics.Path; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; +import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import android.view.ViewConfiguration; -import android.graphics.Path; + +import java.util.Arrays; public class InputService extends AccessibilityService { - /** - * This globally tracks gesture completion status and is _not_ per gesture. - */ - private static class GestureCallback extends AccessibilityService.GestureResultCallback { - private boolean mCompleted = true; // initially true so we can actually dispatch something - - @Override - public synchronized void onCompleted(GestureDescription gestureDescription) { - mCompleted = true; - } - - @Override - public synchronized void onCancelled(GestureDescription gestureDescription) { - mCompleted = true; - } - } - - private static final String TAG = "InputService"; - - private static InputService instance; - - private Handler mMainHandler; - - private boolean mIsButtonOneDown; - private Path mPath; - private long mLastGestureStartTime; - - private boolean mIsKeyCtrlDown; - private boolean mIsKeyAltDown; - private boolean mIsKeyShiftDown; - private boolean mIsKeyDelDown; - private boolean mIsKeyEscDown; - - private float mScaling; - - private final GestureCallback mGestureCallback = new GestureCallback(); - - - @Override - public void onAccessibilityEvent( AccessibilityEvent event ) { } - - @Override - public void onInterrupt() { } - - @Override - public void onServiceConnected() - { - super.onServiceConnected(); - instance = this; - mMainHandler = new Handler(instance.getMainLooper()); - Log.i(TAG, "onServiceConnected"); - } - - @Override - public void onDestroy() { - super.onDestroy(); - instance = null; - Log.i(TAG, "onDestroy"); - } - - public static boolean isEnabled() - { - return instance != null; - } - - /** - * Set scaling factor that's applied to incoming pointer events by dividing coordinates by - * the given factor. - * @param scaling The scaling factor as a real number. - * @return Whether scaling was applied or not. - */ - public static boolean setScaling(float scaling) { - try { - instance.mScaling = scaling; - return true; - } catch (Exception e) { - return false; - } - } - - @SuppressWarnings("unused") - public static void onPointerEvent(int buttonMask, int x, int y, long client) { - - try { - x /= instance.mScaling; - y /= instance.mScaling; + /** + * This globally tracks gesture completion status and is _not_ per gesture. + */ + private static class GestureCallback extends AccessibilityService.GestureResultCallback { + private boolean mCompleted = true; // initially true so we can actually dispatch something + + @Override + public synchronized void onCompleted(GestureDescription gestureDescription) { + mCompleted = true; + } + + @Override + public synchronized void onCancelled(GestureDescription gestureDescription) { + mCompleted = true; + } + } + + private static final String TAG = "InputService"; + + private static InputService instance; + + private Handler mMainHandler; + + private boolean mIsButtonOneDown; + private Path mPath; + private long mLastGestureStartTime; + + // FIXME: I don't know how to get out of this situation without using the static keyword. + // It might be worth moving this block into the HotkeyHandler class and leaving it static, + // but I can't imagine a good way to do this. + private static final HotkeyBinding[] HOTKEY_BINDINGS = new HotkeyBinding[]{ + new HotkeyBinding("Portrait/Landscape Workaround", "hotkey_portrait_landscape_Workaround_", + VNCKey.Ctrl, VNCKey.Alt, VNCKey.Del, + () -> instance.mMainHandler.post(MainService::togglePortraitInLandscapeWorkaround)), + + new HotkeyBinding("Show Recents", "hotkey_show_recents_", + VNCKey.Ctrl, VNCKey.Shift, VNCKey.Esc, + () -> instance.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)), + + new HotkeyBinding("Back", "hotkey_back_", + VNCKey.Esc, VNCKey.Empty, VNCKey.Empty, + () -> instance.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)), + + new HotkeyBinding("Home", "hotkey_home_", + VNCKey.Home, VNCKey.Empty, VNCKey.Empty, + () -> instance.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)) + }; + + private float mScaling; + + private final GestureCallback mGestureCallback = new GestureCallback(); + + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + } + + @Override + public void onInterrupt() { + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + instance = this; + Arrays.sort(HOTKEY_BINDINGS); + mMainHandler = new Handler(instance.getMainLooper()); + Log.i(TAG, "onServiceConnected"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + instance = null; + Log.i(TAG, "onDestroy"); + } + + public static boolean isEnabled() { + return instance != null; + } + + /** + * Set scaling factor that's applied to incoming pointer events by dividing coordinates by + * the given factor. + * + * @param scaling The scaling factor as a real number. + * @return Whether scaling was applied or not. + */ + public static boolean setScaling(float scaling) { + try { + instance.mScaling = scaling; + return true; + } catch (Exception e) { + return false; + } + } + + @SuppressWarnings("unused") + public static void onPointerEvent(int buttonMask, int x, int y, long client) { + + try { + x /= instance.mScaling; + y /= instance.mScaling; /* left mouse button */ - // down, was up - if ((buttonMask & (1 << 0)) != 0 && !instance.mIsButtonOneDown) { - instance.mIsButtonOneDown = true; - instance.startGesture(x, y); - } + // down, was up + if ((buttonMask & (1 << 0)) != 0 && !instance.mIsButtonOneDown) { + instance.mIsButtonOneDown = true; + instance.startGesture(x, y); + } - // down, was down - if ((buttonMask & (1 << 0)) != 0 && instance.mIsButtonOneDown) { - instance.continueGesture(x, y); - } + // down, was down + if ((buttonMask & (1 << 0)) != 0 && instance.mIsButtonOneDown) { + instance.continueGesture(x, y); + } - // up, was down - if ((buttonMask & (1 << 0)) == 0 && instance.mIsButtonOneDown) { - instance.mIsButtonOneDown = false; - instance.endGesture(x, y); - } + // up, was down + if ((buttonMask & (1 << 0)) == 0 && instance.mIsButtonOneDown) { + instance.mIsButtonOneDown = false; + instance.endGesture(x, y); + } - // right mouse button - if ((buttonMask & (1 << 2)) != 0) { - instance.longPress(x, y); - } + // right mouse button + if ((buttonMask & (1 << 2)) != 0) { + instance.longPress(x, y); + } - // scroll up - if ((buttonMask & (1 << 3)) != 0) { + // scroll up + if ((buttonMask & (1 << 3)) != 0) { - DisplayMetrics displayMetrics = new DisplayMetrics(); - WindowManager wm = (WindowManager) instance.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getRealMetrics(displayMetrics); + DisplayMetrics displayMetrics = new DisplayMetrics(); + WindowManager wm = (WindowManager) instance.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getRealMetrics(displayMetrics); - instance.scroll(x, y, -displayMetrics.heightPixels / 2); - } + instance.scroll(x, y, -displayMetrics.heightPixels / 2); + } - // scroll down - if ((buttonMask & (1 << 4)) != 0) { + // scroll down + if ((buttonMask & (1 << 4)) != 0) { - DisplayMetrics displayMetrics = new DisplayMetrics(); - WindowManager wm = (WindowManager) instance.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getRealMetrics(displayMetrics); + DisplayMetrics displayMetrics = new DisplayMetrics(); + WindowManager wm = (WindowManager) instance.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getRealMetrics(displayMetrics); - instance.scroll(x, y, displayMetrics.heightPixels / 2); - } - } catch (Exception e) { - // instance probably null - Log.e(TAG, "onPointerEvent: failed: " + Log.getStackTraceString(e)); - } - } - - public static void onKeyEvent(int down, long keysym, long client) { - Log.d(TAG, "onKeyEvent: keysym " + keysym + " down " + down + " by client " + client); + instance.scroll(x, y, displayMetrics.heightPixels / 2); + } + } catch (Exception e) { + // instance probably null + Log.e(TAG, "onPointerEvent: failed: " + Log.getStackTraceString(e)); + } + } + public static void onKeyEvent(int down, long keysym, long client) { + Log.d(TAG, "onKeyEvent: keysym " + keysym + " down " + down + " by client " + client); /* Special key handling. */ - try { + try { /* Save states of some keys for combo handling. */ - if(keysym == 0xFFE3) - instance.mIsKeyCtrlDown = down != 0; - - if(keysym == 0xFFE9 || keysym == 0xFF7E) // MacOS clients send Alt as 0xFF7E - instance.mIsKeyAltDown = down != 0; - - if(keysym == 0xFFE1) - instance.mIsKeyShiftDown = down != 0; - - if(keysym == 0xFFFF) - instance.mIsKeyDelDown = down != 0; - - if(keysym == 0xFF1B) - instance.mIsKeyEscDown = down != 0; - - /* - Ctrl-Alt-Del combo. - */ - if(instance.mIsKeyCtrlDown && instance.mIsKeyAltDown && instance.mIsKeyDelDown) { - Log.i(TAG, "onKeyEvent: got Ctrl-Alt-Del"); - instance.mMainHandler.post(MainService::togglePortraitInLandscapeWorkaround); - } - - /* - Ctrl-Shift-Esc combo. - */ - if(instance.mIsKeyCtrlDown && instance.mIsKeyShiftDown && instance.mIsKeyEscDown) { - Log.i(TAG, "onKeyEvent: got Ctrl-Shift-Esc"); - instance.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS); - } - - /* - Home/Pos1 - */ - if (keysym == 0xFF50 && down != 0) { - Log.i(TAG, "onKeyEvent: got Home/Pos1"); - instance.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME); - } - - /* - Esc - */ - if(keysym == 0xFF1B && down != 0) { - Log.i(TAG, "onKeyEvent: got Home/Pos1"); - instance.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); - } - - } catch (Exception e) { - // instance probably null - Log.e(TAG, "onKeyEvent: failed: " + e); - } - } - - public static void onCutText(String text, long client) { - Log.d(TAG, "onCutText: text '" + text + "' by client " + client); - - try { - instance.mMainHandler.post(() -> ((ClipboardManager) instance.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText(text, text))); - } catch (Exception e) { - // instance probably null - Log.e(TAG, "onCutText: failed: " + e); - } - } - - private void startGesture(int x, int y) { - mPath = new Path(); - mPath.moveTo( x, y ); - mLastGestureStartTime = System.currentTimeMillis(); - } - - private void continueGesture(int x, int y) { - mPath.lineTo( x, y ); - } - - private void endGesture(int x, int y) { - mPath.lineTo( x, y ); - long duration = System.currentTimeMillis() - mLastGestureStartTime; - // gesture ended very very shortly after start (< 1ms). make it 1ms to get dispatched to the system - if (duration == 0) duration = 1; - GestureDescription.StrokeDescription stroke = new GestureDescription.StrokeDescription( mPath, 0, duration); - GestureDescription.Builder builder = new GestureDescription.Builder(); - builder.addStroke(stroke); - dispatchGesture(builder.build(), null, null); - } - - - private void longPress( int x, int y ) - { - dispatchGesture( createClick( x, y, ViewConfiguration.getTapTimeout() + ViewConfiguration.getLongPressTimeout()), null, null ); - } - - private void scroll( int x, int y, int scrollAmount ) - { - /* - Ignore if another gesture is still ongoing. Especially true for scroll events: - These mouse button 4,5 events come per each virtual scroll wheel click, an incoming - event would cancel the preceding one, only actually scrolling when the user stopped - scrolling. - */ - if(!mGestureCallback.mCompleted) - return; - - mGestureCallback.mCompleted = false; - dispatchGesture(createSwipe(x, y, x, y - scrollAmount, ViewConfiguration.getScrollDefaultDelay()), mGestureCallback, null); - } - - private static GestureDescription createClick( int x, int y, int duration ) - { - Path clickPath = new Path(); - clickPath.moveTo( x, y ); - GestureDescription.StrokeDescription clickStroke = new GestureDescription.StrokeDescription( clickPath, 0, duration ); - GestureDescription.Builder clickBuilder = new GestureDescription.Builder(); - clickBuilder.addStroke( clickStroke ); - return clickBuilder.build(); - } - - private static GestureDescription createSwipe( int x1, int y1, int x2, int y2, int duration ) - { - Path swipePath = new Path(); - - x1 = Math.max(x1, 0); - y1 = Math.max(y1, 0); - x2 = Math.max(x2, 0); - y2 = Math.max(y2, 0); - - swipePath.moveTo( x1, y1 ); - swipePath.lineTo( x2, y2 ); - GestureDescription.StrokeDescription swipeStroke = new GestureDescription.StrokeDescription( swipePath, 0, duration ); - GestureDescription.Builder swipeBuilder = new GestureDescription.Builder(); - swipeBuilder.addStroke( swipeStroke ); - return swipeBuilder.build(); - } + boolean isKeyExist = VNCKey.tryChangeKeyState(keysym, down); + + if (isKeyExist) { + for (HotkeyBinding handler : HOTKEY_BINDINGS) { + boolean isCommandExecuted = handler.tryExecute(); + + if (isCommandExecuted) { + Log.i(TAG, handler.PREFS_NAME + "X executed"); + break; + } + } + } + + } catch (Exception e) { + // instance probably null + Log.e(TAG, "onKeyEvent: failed: " + e); + } + } + + public static HotkeyBinding[] getShortcutHandlers() { + return HOTKEY_BINDINGS; + } + + public static void onCutText(String text, long client) { + Log.d(TAG, "onCutText: text '" + text + "' by client " + client); + + try { + instance.mMainHandler.post(() -> ((ClipboardManager) instance.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText(text, text))); + } catch (Exception e) { + // instance probably null + Log.e(TAG, "onCutText: failed: " + e); + } + } + + private void startGesture(int x, int y) { + mPath = new Path(); + mPath.moveTo(x, y); + mLastGestureStartTime = System.currentTimeMillis(); + } + + private void continueGesture(int x, int y) { + mPath.lineTo(x, y); + } + + private void endGesture(int x, int y) { + mPath.lineTo(x, y); + long duration = System.currentTimeMillis() - mLastGestureStartTime; + // gesture ended very very shortly after start (< 1ms). make it 1ms to get dispatched to the system + if (duration == 0) duration = 1; + GestureDescription.StrokeDescription stroke = new GestureDescription.StrokeDescription(mPath, 0, duration); + GestureDescription.Builder builder = new GestureDescription.Builder(); + builder.addStroke(stroke); + dispatchGesture(builder.build(), null, null); + } + + private void longPress(int x, int y) { + int duration = ViewConfiguration.getTapTimeout() + ViewConfiguration.getLongPressTimeout(); + dispatchGesture(createClick(x, y, duration), null, null); + } + + private void scroll(int x, int y, int scrollAmount) { + /* + Ignore if another gesture is still ongoing. Especially true for scroll events: + These mouse button 4,5 events come per each virtual scroll wheel click, an incoming + event would cancel the preceding one, only actually scrolling when the user stopped + scrolling. + */ + if (!mGestureCallback.mCompleted) + return; + + mGestureCallback.mCompleted = false; + dispatchGesture(createSwipe(x, y, x, y - scrollAmount, ViewConfiguration.getScrollDefaultDelay()), mGestureCallback, null); + } + + private static GestureDescription createClick(int x, int y, int duration) { + Path clickPath = new Path(); + + clickPath.moveTo(x, y); + GestureDescription.StrokeDescription clickStroke = new GestureDescription.StrokeDescription(clickPath, 0, duration); + GestureDescription.Builder clickBuilder = new GestureDescription.Builder(); + clickBuilder.addStroke(clickStroke); + + return clickBuilder.build(); + } + + private static GestureDescription createSwipe(int x1, int y1, int x2, int y2, int duration) { + Path swipePath = new Path(); + + x1 = Math.max(x1, 0); + y1 = Math.max(y1, 0); + x2 = Math.max(x2, 0); + y2 = Math.max(y2, 0); + + swipePath.moveTo(x1, y1); + swipePath.lineTo(x2, y2); + GestureDescription.StrokeDescription swipeStroke = new GestureDescription.StrokeDescription(swipePath, 0, duration); + GestureDescription.Builder swipeBuilder = new GestureDescription.Builder(); + swipeBuilder.addStroke(swipeStroke); + + return swipeBuilder.build(); + } } diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java index a70be1bf..13b0b1bf 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java @@ -36,10 +36,14 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -72,6 +76,77 @@ protected void onCreate(Bundle savedInstanceState) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + /*---------------- start of hotkey editor logic ----------------*/ + + LinearLayout hotkeysEditPanel = findViewById(R.id.hotkeysEditPanel); + + for (HotkeyBinding handler : InputService.getShortcutHandlers()) { + + LinearLayout mainVComposer = new LinearLayout(this); + mainVComposer.setOrientation(LinearLayout.VERTICAL); + + TextView nameLabel = new TextView(this); + nameLabel.setText(handler.friendlyName); + mainVComposer.addView(nameLabel); + + // loading saved hotkeys settings + LinearLayout horizontalComposer = new LinearLayout(this); + horizontalComposer.setOrientation(LinearLayout.HORIZONTAL); + for (int i = 0; i < 3; i++) { + VNCKey key = VNCKey.getKeyByName(prefs.getString(handler.PREFS_NAME + i, handler.getKey(i).toString())); + handler.setKey(i, key); + } + + // Used to evenly place selectors on a view + LinearLayout.LayoutParams keySelectorLayout = new LinearLayout.LayoutParams ( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + 1.0f + ); + + for (int i = 0; i < 3; i++) { + Spinner keySelector = new Spinner(this); + ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, VNCKey.toStringsArray()); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_item); + keySelector.setAdapter(adapter); + keySelector.setLayoutParams(keySelectorLayout); + keySelector.setTag(i); + + int selectionPosition = adapter.getPosition(handler.getKey((int) keySelector.getTag()).toString()); + keySelector.setSelection(selectionPosition); + + keySelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + int keyIndex = (int) keySelector.getTag(); + String keyName = keySelector.getSelectedItem().toString(); + SharedPreferences.Editor ed = prefs.edit(); + ed.putString(handler.PREFS_NAME + keyIndex, keyName); + ed.apply(); + + VNCKey selectedKey = VNCKey.getKeyByName(keyName); + handler.setKey(keyIndex, selectedKey); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + horizontalComposer.addView(keySelector); + } + mainVComposer.addView(horizontalComposer); + hotkeysEditPanel.addView(mainVComposer); + } + + SwitchMaterial showEditPanel = findViewById(R.id.showEditPanel); + + showEditPanel.setOnCheckedChangeListener((v, checked) -> { + hotkeysEditPanel.setVisibility(checked ? View.VISIBLE : View.GONE ); + }); + + /*---------------- end of hotkey editor logic ----------------*/ + mButtonToggle = (Button) findViewById(R.id.toggle); mButtonToggle.setOnClickListener(view -> { diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/VNCKey.java b/app/src/main/java/net/christianbeier/droidvnc_ng/VNCKey.java new file mode 100644 index 00000000..50d753e5 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/VNCKey.java @@ -0,0 +1,84 @@ +package net.christianbeier.droidvnc_ng; + +import java.util.HashMap; + +public enum VNCKey { + /* + List of registered key codes and their corresponding keys. + Easy to edit + */ + Empty(0x0L), + Ctrl(0xFFE3L), + Alt(0xFFE9L), + MacOSAlt(0xFF7EL), + Shift(0xFFE1L), + Del(0xFFFFL), + Esc(0xFF1BL), + Home(0xFF50L), + Tab(0xFF09L), + PageUp(0xFF55L), + PageDown(0xFF56L), + End(0xFF57L), + Backspace(0xFF08L), + F1(0xFFBEL), + F2(0xFFBFL), + F3(0xFFC0L), + F4(0xFFC1L), + F5(0xFFC2L), + F6(0xFFC3L), + F7(0xFFC4L), + F8(0xFFC5L), + F9(0xFFC6L), + F10(0xFFC7L), + F11(0xFFC8L), + F12(0xFFC9L); + + /* + Used for easy access to key states. Helps to get rid of endless if/else constructs + */ + private static final HashMap KeyRegistrar = new HashMap() {{ + for (VNCKey k : VNCKey.values()) + put(k.keysym, k); + }}; + + public final long keysym; + private boolean isDown = false; + + VNCKey(long keysym) { + this.keysym = keysym; + } + + public boolean isNotDown() { + return !isDown; + } + + public void setKeyState(int state) { + isDown = state != 0; + } + + public static boolean tryChangeKeyState(long keysym, int keystate) { + if (KeyRegistrar.containsKey(keysym)) { + KeyRegistrar.get(keysym).setKeyState(keystate); + + return true; + } + return false; + } + + public static String[] toStringsArray() { + String[] result = new String[KeyRegistrar.size()-1]; + VNCKey[] values = VNCKey.values(); + for (int i = 0; i < result.length; i++) { + result[i] = values[i].name(); + } + return result; + } + + public static VNCKey getKeyByName(String keyName) { + try { + return VNCKey.valueOf(keyName); + } catch (Exception ignored) { + return null; + } + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b2b95819..dcfbfeb3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,6 @@ @@ -13,11 +14,17 @@ android:orientation="vertical" android:padding="10dip"> - + + + + + + + + + + + + +