Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,19 @@ class TouchHandlerTest {
private val testPoint = PointF(10f, 10f)

@Before
fun setup() {
fun setup() = setupWithPref()

private fun setupWithPref(
mousePassthrough: Boolean = false,
dragEnabled: Boolean = false,
doubleClickEnabled: Boolean = true,
) {
targetPrefs.edit {
putBoolean("mouse_passthrough", mousePassthrough)
putString("gesture_drag", if (dragEnabled) "remote-scroll" else "none")
putString("gesture_double_tap", if (doubleClickEnabled) "double-click" else "two-left-clicks")
}

instrumentation.runOnMainSync {
mockDispatcher = mockk(relaxed = true)
touchHandler = TouchHandler(FrameView(targetConfigContext), mockDispatcher, AppPreferences(targetContext))
Expand All @@ -59,15 +71,6 @@ class TouchHandlerTest {
mockDispatcher.onXKey(0, 0, false)
}


private fun setupWithPref(mousePassthrough: Boolean = false, dragEnabled: Boolean = false) {
targetPrefs.edit {
putBoolean("mouse_passthrough", mousePassthrough)
putString("gesture_drag", if (dragEnabled) "remote-scroll" else "none")
}
setup()
}

/************************* Finger Gestures *******************************************************/

@Test
Expand All @@ -78,6 +81,16 @@ class TouchHandlerTest {
verify { mockDispatcher.onTap1(testPoint) }
}

@Test
fun singleTapImmediatelySent() {
setupWithPref(doubleClickEnabled = false)

sendDown()
sendUp()

verify { mockDispatcher.onTap1(testPoint) }
}

@Test
fun doubleTap() {
sendDown()
Expand All @@ -88,6 +101,19 @@ class TouchHandlerTest {
verify { mockDispatcher.onDoubleTap(testPoint) }
}

@Test
fun doubleTapAsTwoClicks() {
setupWithPref(doubleClickEnabled = false)

sendDown()
sendUp()
Thread.sleep(Delay.BETWEEN_DOUBLE_TAPS)
sendDown()
sendUp()

verify(exactly = 2) { mockDispatcher.onTap1(testPoint) }
}

@Test
fun twoFingerTap() {
sendDown()
Expand Down Expand Up @@ -519,4 +545,4 @@ class TouchHandlerTest {
private fun sendStylusUp(p: PointF = testPoint) = sendEvent(Factory.obtainUpEvent(downEvent, p))
private fun sendStylusMove(p: PointF) = sendEvent(Factory.obtainMoveEvent(downEvent, p))

}
}
128 changes: 104 additions & 24 deletions app/src/main/java/com/gaurav/avnc/ui/vnc/input/TouchHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,12 @@ class TouchHandler(private val frameView: FrameView, private val dispatcher: Dis
* Finger Gestures (and everything else beside mouse & stylus)
****************************************************************************************/
private val scaleDetector = ScaleGestureDetector(frameView.context, this).apply { isQuickScaleEnabled = false }
private val gestureDetector = GestureDetectorEx(frameView.context, FingerGestureListener(), pref.input.gesture.longPressDetectionEnabled)
private val gestureDetector = GestureDetectorEx(
frameView.context,
FingerGestureListener(),
pref.input.gesture.longPressDetectionEnabled,
pref.input.gesture.doubleClickDetectionEnabled,
)
private val swipeVsScale = SwipeVsScale()
private val longPressSwipeEnabled = pref.input.gesture.longPressSwipeEnabled
private val swipeSensitivity = pref.input.gesture.swipeSensitivity
Expand Down Expand Up @@ -297,7 +302,12 @@ class TouchHandler(private val frameView: FrameView, private val dispatcher: Dis
* [GestureDetectorEx] is used to for this purpose. It internally uses stock
* [GestureDetector], and some custom event processing to detect more gestures.
*/
private class GestureDetectorEx(context: Context, val listener: GestureListenerEx, val enableLongPress: Boolean) {
private class GestureDetectorEx(
context: Context,
val listener: GestureListenerEx,
val enableLongPress: Boolean,
enableDoubleClick: Boolean,
) {

/**
* Detected gestures. Some of these come directly from stock [GestureDetector],
Expand Down Expand Up @@ -365,9 +375,22 @@ class TouchHandler(private val frameView: FrameView, private val dispatcher: Dis
* - (double-tap) (double-tap-swipe)
*
*/
private val innerDetector1 = GestureDetector(context, InnerListener1())
private val innerDetector2 = GestureDetector(context, InnerListener2()).apply { setIsLongpressEnabled(false) }
private val innerDetector3 = GestureDetector(context, InnerListener3()).apply { setIsLongpressEnabled(false) }
private val innerListener1: InnerListener1 = when (enableDoubleClick) {
true -> DoubleTapListener1()
false -> RawListener1()
}
private val innerListener2: InnerListener2 = when (enableDoubleClick) {
true -> DoubleTapListener2()
false -> RawListener2()
}
private val innerListener3: InnerListener3 = when (enableDoubleClick) {
true -> DoubleTapListener3()
false -> RawListener3()
}

private val innerDetector1 = GestureDetector(context, innerListener1)
private val innerDetector2 = GestureDetector(context, innerListener2).apply { setIsLongpressEnabled(false) }
private val innerDetector3 = GestureDetector(context, innerListener3).apply { setIsLongpressEnabled(false) }

private var longPressDetected = false
private var doubleTapDetected = false
Expand All @@ -378,31 +401,54 @@ class TouchHandler(private val frameView: FrameView, private val dispatcher: Dis
private var cumulatedY = 0f
private val multiTapSlopSquare = 30 * 30

/**
* A [GestureDetector.OnGestureListener] just like
* [SimpleOnGestureListener], except it doesn’t implement
* [GestureDetector.OnDoubleTapListener].
*/
private open class RawListener : GestureDetector.OnGestureListener {
override fun onDown(e: MotionEvent): Boolean = false
override fun onShowPress(e: MotionEvent) {}
override fun onSingleTapUp(e: MotionEvent): Boolean = false
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean = false
override fun onLongPress(e: MotionEvent) {}
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean = false
}

private inner class InnerListener1 : SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
listener.onSingleTapConfirmed(e)
return true
}
private interface InnerListener1 : GestureDetector.OnGestureListener

/**
* [InnerListener1] that waits for single taps to be confirmed, and thus
* can detect double taps.
*/
private inner class DoubleTapListener1 : SimpleOnGestureListener(), InnerListener1 {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean = handleSingleTap(e)

override fun onLongPress(e: MotionEvent) {
if (!enableLongPress)
return
override fun onLongPress(e: MotionEvent) = handleLongPress(e)

if (doubleTapDetected)
return // Ignore long-press triggered during double-tap-swipe
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean =
handleFling(velocityX, velocityY)
}

longPressDetected = true
listener.onLongPress(e)
}
/**
* [InnerListener1] that doesn’t wait for single taps to be confirmed,
* and thus can send them immediately to the server.
*/
private inner class RawListener1 : RawListener(), InnerListener1 {
override fun onSingleTapUp(e: MotionEvent): Boolean = handleSingleTap(e)

override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
listener.onFling(velocityX, velocityY)
return true
}
override fun onLongPress(e: MotionEvent) = handleLongPress(e)

override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean =
handleFling(velocityX, velocityY)
}

private inner class InnerListener2 : SimpleOnGestureListener() {
private interface InnerListener2 : GestureDetector.OnGestureListener

/**
* [InnerListener2] that can detect double taps.
*/
private inner class DoubleTapListener2 : SimpleOnGestureListener(), InnerListener2 {
override fun onDoubleTap(e: MotionEvent): Boolean {
doubleTapDetected = true
return true
Expand All @@ -413,10 +459,44 @@ class TouchHandler(private val frameView: FrameView, private val dispatcher: Dis
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float) = handleScroll(e1, e2, dx, dy)
}

private inner class InnerListener3 : SimpleOnGestureListener() {
/**
* [InnerListener2] that handles double taps as two single taps.
*/
private inner class RawListener2 : RawListener(), InnerListener2 {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float) = handleScroll(e1, e2, dx, dy)
}

private interface InnerListener3 : GestureDetector.OnGestureListener

private inner class DoubleTapListener3 : SimpleOnGestureListener(), InnerListener3 {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float) = handleScroll(e1, e2, dx, dy)
}

private inner class RawListener3 : RawListener(), InnerListener3 {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float) = handleScroll(e1, e2, dx, dy)
}

private fun handleLongPress(e: MotionEvent) {
if (!enableLongPress)
return

if (doubleTapDetected)
return // Ignore long-press triggered during double-tap-swipe

longPressDetected = true
listener.onLongPress(e)
}

private fun handleSingleTap(e: MotionEvent): Boolean {
listener.onSingleTapConfirmed(e)
return true
}

private fun handleFling(velocityX: Float, velocityY: Float): Boolean {
listener.onFling(velocityX, velocityY)
return true
}

private fun handleScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean {
e1 ?: return false
if (!scrolling) {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/gaurav/avnc/util/AppPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class AppPreferences(context: Context) {
val swipe2; get() = prefs.getString("gesture_swipe2", "pan")!!
val swipe3; get() = prefs.getString("gesture_swipe3", "pan")!!
val doubleTapSwipe; get() = prefs.getString("gesture_double_tap_swipe", "remote-drag")!!
val doubleClickDetectionEnabled; get() = (doubleTap != "two-left-clicks")
val longPressSwipe; get() = prefs.getString("gesture_long_press_swipe", "none")!!
val longPressSwipeEnabled; get() = (longPressSwipe != "none" && longPress != "left-press")
val longPressDetectionEnabled; get() = (longPress != "none" || longPressSwipeEnabled)
Expand Down Expand Up @@ -177,4 +178,4 @@ class AppPreferences(context: Context) {
putBoolean("run_info_right_meta_keys_migrated", true)
}
}
}
}
2 changes: 2 additions & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
<string name="pref_mouse_back_action_default">Par défaut</string>
<string name="pref_swipe_sensitivity">Sensibilité du défilement</string>
<string name="msg_gesture_style_help"><b>Écran tactile</b>\nEffectuer les actions au point de contact\n\n<b>Pavé tactile</b>\nEffectuer les actions au niveau du pointeur</string>
<string name="msg_gesture_double_tap_help"><b>Aucun</b>\nNe rien faire quand un appui double est détecté.\n\n<b>Deux clics gauches</b>\nNe pas détecter les appuis doubles, ce qui permet d’envoyer immédiatement les appuis simples au serveur.</string>
<string name="msg_drag_gesture_help">L\'attribution d\'une action à ce geste modifiera la détection de la pression longue :
\n
\n<b>Appuyer-tenir-lâcher</b> → Appui long
Expand Down Expand Up @@ -240,4 +241,5 @@
<string name="msg_gesture_style_locked_to_touchpad">Style de geste \'Touchpad\' est actif</string>
<string name="pref_long_press_swipe_disabled_summary">Désactivé par la valeur actuelle du geste <b>Pression longue</b></string>
<string name="pref_toolbar_open_with_swipe">Faites glisser depuis le bord pour ouvrir</string>
<string name="pref_gesture_action_two_left_clicks">Deux clics gauches</string>
</resources>
4 changes: 3 additions & 1 deletion app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@
<item>@string/pref_gesture_action_double_click</item>
<item>@string/pref_gesture_action_middle_click</item>
<item>@string/pref_gesture_action_right_click</item>
<item>@string/pref_gesture_action_two_left_clicks</item>
</string-array>
<string-array name="double_tap_action_values">
<item>none</item>
<item>double-click</item>
<item>middle-click</item>
<item>right-click</item>
<item>two-left-clicks</item>
</string-array>

<string-array name="long_press_action_entries">
Expand Down Expand Up @@ -227,4 +229,4 @@
<item>Item 2</item>
<item>Item 3</item>
</string-array>
</resources>
</resources>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
<string name="msg_button_up_delay_help">Delaying click events can help in some rare cases if an app is not responding to clicks.</string>
<string name="msg_wake_on_lan_help">Wake-on-LAN can be used to remotely power-on a computer.\n\nFirst, configure WoL on remote computer, then enable it in AVNC.\nOnce enabled, WoL magic packet will be automatically sent before connecting to this server.</string>
<string name="msg_gesture_style_help"><b>Touchscreen</b>\nDo actions at touch-point \n\n<b>Touchpad</b>\nDo actions at pointer</string>
<string name="msg_gesture_double_tap_help"><b>None</b>\nDon’t do anything when a double-tap is detected.\n\n<b>Two left-clicks</b>\nDo not detect double taps, which allows sending single taps immediately to the server.</string>
<string name="msg_drag_gesture_help">Assigning an action to this gesture will change Long press detection:\n\n<b>Press-hold-release</b> → Long press\n<b>Press-hold-swipe</b> → Long press and swipe</string>
<string name="msg_shortcut_server_deleted">This server has been deleted</string>
<string name="tip_empty_server_list">Server list is empty.\nClick \'<b>+</b>\' to add a server, or\nuse the top address bar to connect directly.</string>
Expand Down Expand Up @@ -176,6 +177,7 @@
<string name="pref_gesture_action_remote_drag_middle">Drag with middle button</string>
<string name="pref_gesture_action_remote_back_press">Back-press on server</string>
<string name="pref_gesture_action_open_keyboard">Open keyboard</string>
<string name="pref_gesture_action_two_left_clicks">Two left-clicks</string>

<string name="pref_theme">Theme</string>
<string name="pref_theme_option_system">System</string>
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/res/xml/pref_input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
app:useSimpleSummaryProvider="true"
app:widgetLayout="@layout/help_btn" />

<ListPreference
<com.gaurav.avnc.ui.prefs.ListPreferenceEx
app:defaultValue="double-click"
app:entries="@array/double_tap_action_entries"
app:entryValues="@array/double_tap_action_values"
app:key="gesture_double_tap"
app:title="@string/pref_double_tap"
app:widgetLayout="@layout/help_btn"
app:helpMessage="@string/msg_gesture_double_tap_help"
app:useSimpleSummaryProvider="true" />

<ListPreference
Expand Down Expand Up @@ -195,4 +197,4 @@
app:title="@string/pref_km_back_to_escape" />
</PreferenceCategory>

</PreferenceScreen>
</PreferenceScreen>