Compare commits

...

5 commits

Author SHA1 Message Date
Gaurav Ujjwal
698f01aacd Allow GitHub Actions to run on any push
Some checks are pending
Android CI / build (push) Waiting to run
2022-10-04 23:00:37 +05:30
Gaurav Ujjwal
09a4990433 Guard against null events passed to onScroll
It appears that on some devices, e1 can be null, which would result in a
crash. This is probably due to manufacturers messing with input handling
of AOSP.
2022-10-04 23:00:37 +05:30
Felix Yan
1fe3dedc52
Correct typos in app/CMakeLists.txt (#90) 2022-08-22 20:13:18 +05:30
Gaurav Ujjwal
ad6d081a6f Allow client-side cursor movement in view-only mode
With direct-touch off, panning the viewer requires cursor movement.
GitHub Issue: #89
2022-08-16 13:27:26 +05:30
Gaurav Ujjwal
64f06caa09 Improve two-finger-swipe vs scale detection 2022-08-15 23:17:10 +05:30
4 changed files with 118 additions and 10 deletions

View file

@ -1,8 +1,6 @@
name: Android CI
on:
push:
branches: [ master ]
on: push
jobs:
build:

View file

@ -19,7 +19,7 @@ macro(avnc_check_submodule name)
endmacro()
# Requred to enable SIMD support on ARM
# Required to enable SIMD support on ARM
if (CMAKE_ANDROID_ARCH STREQUAL "arm64")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=aarch64-linux-android${ANDROID_NATIVE_API_LEVEL}")
elseif (CMAKE_ANDROID_ARCH STREQUAL "arm")
@ -53,7 +53,7 @@ set(AVNC_LIBSSL_SRC_DIR ${AVNC_EXTERN_DIR}/wolfssl)
set(AVNC_LIBSSL_BUILD_DIR ${CMAKE_BINARY_DIR}/wolfssl)
# CMake support in wolfSSl is still under development, so we have to
# manully set some flags to enable OpenSSL compatibility layer
# manually set some flags to enable OpenSSL compatibility layer
set(WOLFSSL_CRL yes)
set(WOLFSSL_DES3 yes)
add_definitions(-DOPENSSL_ALL -DOPENSSL_EXTRA -DHAVE_CRL -DHAVE_EX_DATA

View file

@ -13,6 +13,9 @@ import android.os.Build
import android.view.*
import com.gaurav.avnc.viewmodel.VncViewModel
import com.gaurav.avnc.vnc.PointerButton
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.max
/**
@ -170,11 +173,13 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
private val gestureDetector = GestureDetector(viewModel.app, this)
private val multiFingerTapDetector = MultiFingerTapDetector()
private val dragDetector = DragDetector()
private val swipeVsScale = SwipeVsScale()
private val dragEnabled = viewModel.pref.input.gesture.dragEnabled
private val swipeSensitivity = viewModel.pref.input.gesture.swipeSensitivity
private fun handleGestureEvent(event: MotionEvent): Boolean {
swipeVsScale.onTouchEvent(event)
dragDetector.onTouchEvent(event)
multiFingerTapDetector.onTouchEvent(event)
scaleDetector.onTouchEvent(event)
@ -184,7 +189,8 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
override fun onScaleBegin(detector: ScaleGestureDetector) = true
override fun onScaleEnd(detector: ScaleGestureDetector) {}
override fun onScale(detector: ScaleGestureDetector): Boolean {
dispatcher.onScale(detector.scaleFactor, detector.focusX, detector.focusY)
if (swipeVsScale.shouldScale())
dispatcher.onScale(detector.scaleFactor, detector.focusX, detector.focusY)
return true
}
@ -214,15 +220,17 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
return true
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, dX: Float, dY: Float): Boolean {
val startPoint = e1.point()
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dX: Float, dY: Float): Boolean {
// As weird as it may be, e1 can be null on some devices
val startPoint = e1?.point() ?: return false
val currentPoint = e2.point()
val normalizedDx = -dX * swipeSensitivity
val normalizedDy = -dY * swipeSensitivity
when (e2.pointerCount) {
1 -> dispatcher.onSwipe1(startPoint, currentPoint, normalizedDx, normalizedDy)
2 -> dispatcher.onSwipe2(startPoint, currentPoint, normalizedDx, normalizedDy)
2 -> if (swipeVsScale.shouldSwipe())
dispatcher.onSwipe2(startPoint, currentPoint, normalizedDx, normalizedDy)
}
multiFingerTapDetector.reset()
@ -342,4 +350,106 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
fingerCount = 0
}
}
/**
* Swipe vs Scale detector.
*
* Many two-finger gestures are detected as both swipe & scale gestures because
* [GestureDetector] & [ScaleGestureDetector] work independently. This works
* very well when two-finger swipe pref is set to 'pan' (default value).
* But when the pref is set to 'remote-scroll', this independent detection
* becomes an issue. When user tries to scale, it frequently triggers remote
* scrolling. And when user tries to scroll, it triggers scaling.
*
* This class tries to clearly differentiate between these two gestures.
* It works by tracking the fingers, and calculating the angle between two paths.
* Then [decide] between two gestures by comparing the angle to some thresholds.
*
* This class can mis-detect some gestures because fingers don't always move
* perfectly, but it does provide huge improvement over existing situation.
*/
private inner class SwipeVsScale {
private val enabled = viewModel.pref.input.gesture.swipe2 == "remote-scroll"
private var detecting = false
private var scaleDetected = false
private var swipeDetected = false
private var f1Id = 0 // Finger 1
private var f2Id = 0 // Finger 2
private val f1Start = PointF()
private val f2Start = PointF()
private val f1Current = PointF()
private val f2Current = PointF()
fun onTouchEvent(e: MotionEvent) {
if (!enabled)
return
when (e.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_CANCEL,
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
detecting = false
scaleDetected = false
swipeDetected = false
}
MotionEvent.ACTION_POINTER_DOWN -> {
detecting = e.pointerCount == 2
if (detecting) {
f1Id = e.getPointerId(0)
f2Id = e.getPointerId(1)
f1Start.set(e.getX(f1Id), e.getY(f1Id))
f2Start.set(e.getX(f2Id), e.getY(f2Id))
f1Current.set(f1Start)
f2Current.set(f2Start)
}
}
MotionEvent.ACTION_MOVE -> {
if (detecting) {
f1Current.set(e.getX(f1Id), e.getY(f1Id))
f2Current.set(e.getX(f2Id), e.getY(f2Id))
}
}
}
}
fun shouldScale(): Boolean {
decide()
return !enabled || (detecting && scaleDetected)
}
fun shouldSwipe(): Boolean {
decide()
return !enabled || (detecting && swipeDetected)
}
/**
* Decides if gesture can be considered a swipe/scale
*/
private fun decide() {
if (!detecting)
return
val t1 = theta(f1Start, f1Current)
val t2 = theta(f2Start, f2Current)
val diff = abs(t1 - t2)
scaleDetected = diff > 45
swipeDetected = diff < 30
}
/**
* Returns the angle made by line [p1]->[p2] with the positive x-axis.
* Returned angle will be in range [0, 360]
*/
private fun theta(p1: PointF, p2: PointF): Double {
val theta = atan2(p2.y - p1.y, p2.x - p1.x)
val degree = (theta / PI) * 180
return (degree + 360) % 360 // Map [-180, 180] to [0, 360]
}
}
}

View file

@ -171,7 +171,7 @@ class VncClient(private val observer: Observer) {
* @param x Horizontal pointer coordinate
* @param y Vertical pointer coordinate
*/
fun moveClientPointer(x: Int, y: Int) = executeSend {
fun moveClientPointer(x: Int, y: Int) {
pointerX = x
pointerY = y
observer.onPointerMoved(x, y)